Numpy: 最初の非ゼロ要素(Trac#1673)

作成日 2012å¹´10月20日  Â·  26コメント  Â·  ソース: numpy/numpy

_不明に割り当てられたtracユーザーtom3118による2010-11-13の元のチケットhttp://projects.scipy.org/numpy/ticket/1673。_

「MATLABユーザー向けのnumpy」は、
nonzero(A)[0][0]
配列Aの最初の非ゼロ要素のインデックスを検索します。

これに伴う問題は、Aが100万要素の長さであり、最初の要素がゼロである可能性があることです。

これは非常に一般的な操作です。 このための効率的な組み込みの方法は非常に便利です。 また、 findが非常に一般的であるMatlabからの移行も容易になります。

01 - Enhancement Other

最も参考になるコメント

これは3年遅れていることは知っていますが、これは現在numpyに含まれていますか? Matlabのバックグラウンドから来ているので、この関数は私にとって本当に重要なようです。 PRをいただければ幸いです(Imは開発者の1人ではありません)。

全てのコメント26件

_tracユーザーtom3118は2010-11-13に書き込みました_

関連するユースケースは次のとおりです。
filter(test,A)[0]
Aが長いか、 testが高い。

_ @rgommersは2011-03-24_に書き込みました

最初にゼロ以外である必要はありません。最初に任意の値が役立ちます。

_ @rgommersは2011-03-24_に書き込みました

#2333に記載されているように、1-Dの意味は明確です。 > 1次元の場合、セマンティクスについては議論の余地があります。

おそらく、軸上の反復の順序を決定するキーワードが機能します。 または、> 1-Dの場合は単純に未定義にすることができます。

_tracユーザーlcampagnは2011-07-09_に書き込みました

numpyでfind_firstに対する多くのリクエストを見てきましたが、これらのリクエストのほとんどは、「x未満の最初の値を検索する」や「ゼロ以外の最初の値を検索する」など、微妙に異なる(互換性のない)要件を持っています。 次の機能仕様をお勧めします。

  ind = array.find(x, testOp='eq', arrayOp='all', axis=0, test=None)
  arguments:
    x       -> value to search for
    testOp  -> condition to test for ('eq', 'ne', 'gt', 'lt', 'ge', 'le')
    arrayOp -> method for joining multiple comparisons ('any' or 'all')
    axis    -> the axis over which to search
    test    -> for convenience, this may specify a function to call to perform
               the test. This is not expected to be efficient.
  returns: 
    first index where condition is true (or test returns true, if given)
    or None if the condition was never met

配列のndim> 1の場合、テストは通常​​のブロードキャストルールを使用して実行されます。
したがって、たとえば、形状が(2,3)の配列がある場合、次のようになります。

  ## find first row with all values=0
  array.find(0, testOp='eq', arrayOp='all', axis=0)
  ## equivalent to:
  for i in range(array.shape[axis]):
    if (array[i] == 0).all():
      return i

  ## find first column with any element greater than its corresponding element in col
  col = array([1,2])
  array.find(col, testOp='gt', arrayOp='any', axis=1)
  ## equivalent to:
  for i in range(array.shape[axis]):
    if (array[:,i] == col.any():
      return i

先日この機能が必要だったので、これをよく調べて、適切に迅速な結果を得るにはCソリューションが必要であると確信しましたが、Pythonで記述されたチャンク化アプローチは適切に迅速であることが証明されました。私の場合、起動するのがより柔軟です。

import numpy as np
from itertools import chain, izip


def find(a, predicate, chunk_size=1024):
    """
    Find the indices of array elements that match the predicate.

    Parameters
    ----------
    a : array_like
        Input data, must be 1D.

    predicate : function
        A function which operates on sections of the given array, returning
        element-wise True or False for each data value.

    chunk_size : integer
        The length of the chunks to use when searching for matching indices.
        For high probability predicates, a smaller number will make this
        function quicker, similarly choose a larger number for low
        probabilities.

    Returns
    -------
    index_generator : generator
        A generator of (indices, data value) tuples which make the predicate
        True.

    See Also
    --------
    where, nonzero

    Notes
    -----
    This function is best used for finding the first, or first few, data values
    which match the predicate.

    Examples
    --------
    >>> a = np.sin(np.linspace(0, np.pi, 200))
    >>> result = find(a, lambda arr: arr > 0.9)
    >>> next(result)
    ((71, ), 0.900479032457)
    >>> np.where(a > 0.9)[0][0]
    71


    """
    if a.ndim != 1:
        raise ValueError('The array must be 1D, not {}.'.format(a.ndim))

    i0 = 0
    chunk_inds = chain(xrange(chunk_size, a.size, chunk_size), 
                 [None])

    for i1 in chunk_inds:
        chunk = a[i0:i1]
        for inds in izip(*predicate(chunk).nonzero()):
            yield (inds[0] + i0, ), chunk[inds]
        i0 = i1
In [1]: from np_utils import find

In [2]: import numpy as np

In [3]: import numpy.random    

In [4]: np.random.seed(1)

In [5]: a = np.random.randn(1e8)

In [6]: a.min(), a.max()
Out[6]: (-6.1194900990552776, 5.9632246301166321)

In [7]: next(find(a, lambda a: np.abs(a) > 6))
Out[7]: ((33105441,), -6.1194900990552776)

In [8]: (np.abs(a) > 6).nonzero()
Out[8]: (array([33105441]),)

In [9]: %timeit (np.abs(a) > 6).nonzero()
1 loops, best of 3: 1.51 s per loop

In [10]: %timeit next(find(a, lambda a: np.abs(a) > 6))
1 loops, best of 3: 912 ms per loop

In [11]: %timeit next(find(a, lambda a: np.abs(a) > 6, chunk_size=100000))
1 loops, best of 3: 470 ms per loop

In [12]: %timeit next(find(a, lambda a: np.abs(a) > 6, chunk_size=1000000))
1 loops, best of 3: 483 ms per loop

これを開発メーリングリストに載せますが、十分な関心があれば、PRに変えることができれば幸いです。

乾杯、

これは3年遅れていることは知っていますが、これは現在numpyに含まれていますか? Matlabのバックグラウンドから来ているので、この関数は私にとって本当に重要なようです。 PRをいただければ幸いです(Imは開発者の1人ではありません)。

私もこれに興味があります。

おそらくそれは明らかですが、言及されていないので、 np.all()とnp.any()は、怠惰にするのがおそらくさらに簡単です(そして次元> 1の場合は明確です)。 現在...

In [2]: zz = np.zeros(shape=10000000)

In [3]: zz[0] = 1

In [4]: %timeit -r 1 -n 1 any(zz)
1 loop, best of 1: 3.52 µs per loop

In [5]: %timeit -r 1 -n 1 np.any(zz)
1 loop, best of 1: 16.7 ms per loop

(申し訳ありませんが、#3446への参照を見逃していました)

私はかなり長い間この問題の効率的な解決策を探していましたが、この機能をサポートする具体的な計画はないようですので、APIが提案したように完全ではなく用途の広い解決策を考え出そうとしました上記(特に今のところ1D配列のみをサポート)ですが、これには完全にCで記述されているという利点があるため、かなり効率的です。

ソースと詳細はここにあります:

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

実装に関するコメント、特にブール配列を渡して最初の真の値を検索するときのパフォーマンスの問題についてのコメントに感謝します。これは、そのPyPiページで説明されています。

私はこれを、7万回以上閲覧されているこの機能を探しているstackexchangeの投稿から見つけました。 @roebelこれについてフィードバックを受け取ったことはありますか? この機能のPRを入れるだけで、もっと注目されるかもしれません。

いいえ、フィードバックはありませんでしたが、何人かの人が問題なくパッケージを使用したようです。
ところで、anacondalinuxとmacos用にanacondaインストーラーを作成しました

https://anaconda.org/roebel/py_find_1st

PRに関しては、簡単にnumpyにマージできるようにこれを適応させるために必要な努力を検討する必要があります。 APIの変更と拡張についての議論を通して戦う時間はありません。

「 priority:normal 」の削除は、この重要な機能がどういうわけかあまり注目されないことを意味しますか?

優先順位は、ラベルがないだけで、依然として「通常」です。 この問題には、実際にPRを作成し、ドキュメントやできればベンチマークを含む承認プロセスを進めるためのチャンピオンが必要です。

おそらくここで#8528を指すのに役立ちます。これは、名目上は約all_equalですが、これの一部を実装していると見なすことができます。 実際、 https: //github.com/numpy/numpy/pull/8528#issuecomment -365358119で、 @ ahaldaneは、新しいgufunc all_equalの代わりに、すべての比較演算子にfirst削減メソッドを実装することを明示的に提案しています。 all_equal 。

これはまた、適応を待っている実装がかなりあることを意味します(ただし、gufuncから新しいreductionメソッドへの些細な変更ではなく、すべてのufuncに新しいメソッドが必要かどうかという疑問があります。どのfirstはほとんど意味がありません。

この問題は(少なくとも)2012年から知られています。 nonzero(A)[0][0]がすべてのAを検索しないようにするための更新はありますか?

常にすべての要素をスキャンする、いわゆるPythonの方法ですか?

@yunyoulu :それはufuncの方法です。 一歩下がって、numpyでのマルチステップ計算の一般的なプロセスと、それにかかるパスの数を見てみましょう。

  1. np.argwhere(x)[0] -データの1パスを実行します
  2. np.argwhere(f(x))[0] -2回のデータパスを実行します
  3. np.argwhere(f(g(x)))[0] -データの3パスを実行します

1つのオプションは、 np.first関数などを導入することです。これは、次のようになります。ここで、 k <= 1は、最初の要素がどこにあるかによって異なります。

  1. np.first(x)[0] -データの0 + kパスを実行します
  2. np.first(f(x))[0] -データの1 + kパスを実行します
  3. np.first(f(g(x)))[0] -データの2 + kパスを実行します

ここで質問するのは、この節約は本当にそれだけの価値があるのでしょうか。 Numpyは基本的に怠惰なコンピューティングプラットフォームではなく、計算の最後のステップを怠惰にすることは、前のすべてのステップがそうでなかったとしても、特に価値はありません。


時代遅れ

@ eric-wieser

私はそれが完全に正しい言葉であるとは思わない。 何らかの問題でk = 10の場合、 np.first(f(x))[0]のデータの1+10=11パスではありません

(簡潔にするために@ eric-wieserによって編集されましたが、この会話はすでに長すぎます)

この機能の必要性を最もよく理解しているユースケースは、 AがA.shape = (n_1, n_2, ..., n_m)の大きなテンソルである場合です。 このような場合、 np.first(A)は、 n_1*n_2*...*n_m $ではなくAのk要素のみを調べる必要があります(大幅な節約になる可能性があります)。

この機能の必要性は、Aが大きなテンソルである場合に最もよくわかります。

おそらくこの場合、データのフルパスを少なくとも1回実行しているので、せいぜい2倍の速度で実行されるコードを取得できます。

ここで質問するのは、この節約は本当にそれだけの価値があるのでしょうか。 Numpyは基本的に怠惰なコンピューティングプラットフォームではなく、計算の最後のステップを怠惰にすることは、前のすべてのステップがそうでなかったとしても、特に価値はありません。

これは興味深い観点であり、確立された場合、「他の何かも計算しているが、それでもまだ遅いため」、計算パフォーマンスを改善するためのほとんどすべての努力を無駄にすることを正当化するために使用できます。 (これは、気候変動対策の否定者が使用するのと同じ議論です。まあ、この他の国が何かをするまで、私たちの国で何かをすることは誰にも役立ちません。)私はまったく確信していません。 計算の一部を1 / k高速化する可能性があり、kが非常に小さい可能性がある場合、それは私の意見では価値があります。

また、インタラクティブに作業する場合(Jupyterなど)、データの「パス」を別々のセルで行うことが非常に多いため、セル全体の速度も向上する可能性があります。

np.first(f(x))[0] -データの1 + kパスを実行します

@ eric-wieser確かに、2017年にこの問題を調べたとき、それが一種のnp.firstwhere(x, array_or_value_to_compare)への第一歩になることを本当に望んでいました。これは確かに特定のケースですが、私の経験では重要ですが、 f(x) 。

@toobaz :その例ではf = lambda x: x == value_to_compareがあると思います。

これがまさに、私がこの道を進むことをまったく警戒している理由です(cc @bersbersbers)。 注意しないと、(投機的なスペル)になってしまいます。

  1. np.first(x) -パスとゼロ以外を保存
  2. np.first_equal(x, v) -パスを保存するvs first(np.equal(x, v))
  3. np.first_square_equal(x*x, v) -パスを保存するvs first_equal(np.square(x), v)

これがまったくスケーリングされないことはかなり明白なはずであり、どこかに線を引く必要があります。 私は1が許可されることに少し賛成ですが、2が許可されることは、すでにAPI表面積の爆発的増加であり、3は私には非常に賢明ではないようです。

np.firstを支持する1つの引数-それを実装する場合、 numbaは、 np.first(x*x == v)が_numbaコンテキスト内で_実際にシングルパスを実行するように特殊なケースにすることができます。

とにかく、問題の現在の状況を明らかにする、numpyで怠惰なことをすることは不可能であることを知っておくのは良いことです。

ただし、パフォーマンスの微調整をスケーラビリティーのみで考慮した場合、私は快適ではありません。

簡単な質問をしてみましょう。今日、パーソナルコンピュータは拡張されていますか? 答えは間違いなくNOです。 3年前に標準のラップトップを購入すると、8GBのメモリが搭載されています。 そして今でも市場には8GBがあります。 ただし、すべてのソフトウェアは、以前の2倍または4倍のメモリを使用しています。 少なくともワークステーションは、クラスターと同じようにスケーリングしていません。

複雑さをまったく変えずに関数を10倍遅くすることは、1人のデータサイエンティストを夢中にさせるのに十分です。 さらに悪いことに、プロファイリングによってボトルネックが特定されたとしても、彼ができるエレガントなことは何もありません。

私が詳しく説明しようとしているのは、怠惰な処理を行う能力を持つことが常に望ましく、システムの応答性と言語を使用する人々の生産性にとって重要になる可能性があるということです。 ライブラリ開発の難しさや作業負荷は、これらの機能を実装しないことの非常に良い言い訳になり、確かに理解できますが、それらが役に立たないとは言わないでください。

@toobaz :その例ではf = lambda x: x == value_to_compareがあると思います。

正しい

これがまさに、私がこの道を進むことをまったく警戒している理由です(cc @bersbersbers)。 注意しないと、(投機的なスペル)になってしまいます。

1. `np.first(x)` - save a pass vs nonzero

2. `np.first_equal(x, v)` - save a pass vs `first(np.equal(x, v))`

3. `np.first_square_equal(x*x, v)` - save a pass vs `first_equal(np.square(x), v)`

私はあなたの懸念を理解していますが、 np.square_whereを要求することは決してない(そして誰も要求しない)のとまったく同じようにnp.first_square_equalを要求することは決してありません。 はい、そうです。3を実行すると、データのフルパスを作成することを意味します。ただし、 vは一度作成されるため、 xのさまざまな値を探す必要があるかもしれません。 。 たとえば(簡単にするために例2に戻ります)、30個の可能なカテゴリすべてが10 ^ 9アイテム配列に表示されるかどうかを確認したいと思います。そして、それらすべてが最初の10 ^ 3要素に表示されることを強く疑っています。

それで、最初に私の前のコメントを明確にしましょう:私の直感を満たす関数としてnp.firstwhere(x, array_or_value_to_compare)が欲しいのですが、2017年に私が持っていた計算上の問題はnp.firstだけでも解決されたでしょう。

第二に、ポイントは-私が思うに-単一の呼び出しの実行時間だけではありません。 とにかく2と3を実行するには、データのフルパスを作成する必要があるのは事実です...しかし、データを初期化したときにすでにこのパスを実行した可能性があり、今は本当にスピードアップする方法を探しています頻繁な操作。

np.firstが標準のnumpyアプローチから実際に逸脱しているというあなたの指摘を理解しました。うまく実装するのは簡単ではないかもしれません...私にはわかりませんが、APIの残りの部分にどのように「感染」するかです。または独自の大きなAPIを成長させます。

とはいえ、代わりにそれが本当にnumpyスコープを超えていると思う場合は、代わりに小さな独立したパッケージからのスコープがあるかもしれません。

こんにちはポール、

私はあなたのソリューションをnp.flatnonzeroと私のpy_find_1st拡張機能と比較する小さなベンチマークを作成しました。

ベンチマークが添付されています。

ここに結果があります

(ベース)m3088.roebel :(テスト)(g:master)514> ./benchmark.py
utf1st.find_1st(rr、limit、utf1st.cmp_equal)::
ランタイム0.131秒
np.flatnonzero(rr == limit)[0] ::
ランタイム2.121s
next((ii for ii、vv in enumerate(rr)if vv == limit))::
ランタイム1.612s

したがって、提案されたソリューションは、必要がないため、flatnonzeroよりも25%高速です。
結果の配列を作成しても、py_find_1st.find_1stよりも約12遅いです。

一番
アクセル

編集:
メールで返信したメッセージが消えたようで、メールにもベンチマークが付いています。 ベンチマークはこちら

https://github.com/roebel/py_find_1st/blob/master/test/benchmark.py

騒音でごめんなさい。

15/05/2020 17:33に、PKは次のように書いています。

| next(i for i、v in enumerate(x)if v)|はどうですか?

—
あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信するか、GitHub https://github.com/numpy/numpy/issues/2269#issuecomment-629314457で表示するか、登録を解除してください
https://github.com/notifications/unsubscribe-auth/ACAL2LS2YZALARHBHNABVILRRVOEPANCNFSM4ABV5HGA 。

このページは役に立ちましたか?
0 / 5 - 0 評価