既存の値より大きい値の最初のNumpy発生


144

numpyに1D配列があり、値がnumpy配列の値を超えるインデックスの位置を見つけたいです。

例えば

aa = range(-10,10)

aa5が超過する場所を見つけます。


2
ambrusがコメントしたように、たとえばargmaxの回答がその場合には機能しないため(max of(0,0,0,0)= 0)、解決策がないかどうかを明確にする必要があります
seanv507

回答:


199

これは少し高速です(そして見栄えがします)

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

103
注意点:入力配列にTrue値がない場合、np.argmaxは喜んで0を返します(これは、この場合は必要ありません)。
アンブルス2014

8
結果は正しいですが、説明は少し疑わしいと思います。argmax最初は止まらないようですTrue。(これはTrue、異なる位置に単一のブール配列を作成することでテストできます。)速度は、おそらくargmax出力リストを作成する必要がないという事実によって説明されます。
DrV 2014年

1
私はあなたが正しいと思います、@ DrV。私の説明は、元の意図が実際に最大値を求めていないにもかかわらず、正しい結果が得られる理由に関するものであり、の内部の詳細を理解できないと主張できないために高速である理由ではありませんargmax
askewchan 2014年

1
@ジョージ、正確に理由がわからない。私が示した特定の例ではそれが速いとしか言​​えないので、(i)理由がわからない(@DrVのコメントを参照)、または(ii)より多くのケースをテストする(たとえば、aaソートされているかどうか、 @Michaelの答えのように)。
askewchan 2017

3
@DrV、私はNumPy 1.11.2を使用して異なる位置にargmax単一の1000万要素のブール配列Trueと問題の位置を実行しましたTrue。したがって、1.11.2 argmaxはブール配列で「短絡」しているようです。
Ulrich Stern

96

配列のコンテンツを並べ替えると、さらに高速なメソッド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

19
これは、配列がソートされていることを前提とした実際の最良の回答です(実際には質問では指定されていません)。あなたは厄介を避けることができる+1np.searchsorted(..., side='right')
askewchan

3
sideソートされた配列に繰り返し値がある場合にのみ、引数が違いを生むと思います。返されるインデックスの意味は変わりません。これは常にクエリ値を挿入できるインデックスであり、後続のすべてのエントリを右にシフトし、ソートされた配列を維持します。
ガス2015年

@Gus sideは、並べ替えられた配列と挿入された配列の両方に同じ値が存在する場合、どちらの値が繰り返されているかに関係なく効果があります。並べ替えられた配列で繰り返される値は、効果を誇張するだけです(側面間の差は、挿入される値が並べ替えられた配列に表示される回数です)。side それはそれらのインデックスでソートされた配列に値を挿入することから生じる配列を変更しないものの、戻されるインデックスの意味を変更します。微妙だが重要な違い。実際、にN/2ない場合、この答えは間違ったインデックスを与えaaます。
askewchan 2017

上記のコメントで示唆されているように、にN/2ない場合、この回答は1つずれていaaます。正しい形式はnp.searchsorted(aa, N/2, side='right')(なし+1)です。それ以外の場合、両方の形式は同じインデックスを与えます。N奇妙なテストケースを考えます(N/2.0Python 2を使用している場合は強制的にフロートします)。
askewchan 2017

21

私もこれに興味があり、提案されたすべての回答を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)'
    )

4
np.searchsorted一定時間ではありません。それは実際O(log(n))です。ただし、テストケースは実際にはsearchsorted(つまりO(1))のベストケースをベンチマークします。
MSeifert

@MSeifert O(log(n))を表示するには、どのような入力配列/アルファが必要ですか?
NicoSchlömer18年

1
インデックスsqrt(length)でアイテムを取得すると、パフォーマンスが非常に低下しました。 そのベンチマークを含めて、ここにも回答を書きました。
MSeifert

searchsorted(または任意のアルゴリズム)がO(log(n))、並べ替えられた均一に分散されたデータのバイナリ検索に勝ることはないでしょう。編集:searchsorted あるバイナリ検索。
Mateen Ulhaq 2018年

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

要素間に一定のステップがある配列

以下の場合、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を使用した一般的なソリューション

より一般的なアプローチは、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関数によってパフォーマンスが向上します。

アイテムはsqrt(len)にあります

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配列のための有効なインデックスではありませんインデックスを返します。

関数whereminnonzeroおよびcalculate例外をスローします。ただし、の例外のみがcalculate実際に役立つ情報を示しています。

つまり、少なくとも値が配列内にあるかどうかわからない場合は、例外または無効な戻り値をキャッチして適切に処理する適切なラッパー関数でこれらの呼び出しを実際にラップする必要があります。


注:計算searchsortedオプションは、特別な条件でのみ機能します。「計算」関数は一定のステップを必要とし、searchsortedは配列をソートする必要があります。したがって、これらは適切な状況で役立つ可能性がありますが、この問題の一般的な解決策ではありません。ソート済みの Pythonリストを扱っている場合は、Numpys searchsortedを使用する代わりに、bisectモジュールを確認することをお勧めします。


3

提案したい

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

これは、条件が満たされた場合に最小のインデックスを返し、条件が満たされない場合は無限大をwhere返します(空の配列を返します)。


1

私は一緒に行きます

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

ここで、Vはベクトル(1次元配列)、xは値で、iは結果のインデックスです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.