67

Numpy 配列に特定の行のインスタンスが少なくとも 1 つ含まれているかどうかを確認する Pythonic で効率的な方法はありますか? 「効率的」とは、結果が既に見つかっている場合でも、配列全体を反復処理するのではなく、最初に一致する行を見つけたときに終了することを意味します。

Python 配列を使用するとif row in array:、これを非常にきれいに実行できますが、以下に示すように、Numpy 配列に期待するようには機能しません。

Python 配列の場合:

>>> a = [[1,2],[10,20],[100,200]]
>>> [1,2] in a
True
>>> [1,20] in a
False

しかし、Numpy 配列は異なる、かなり奇妙に見える結果をもたらします。(の__contains__方法はndarray文書化されていないようです。)

>>> a = np.array([[1,2],[10,20],[100,200]])
>>> np.array([1,2]) in a
True
>>> np.array([1,20]) in a
True
>>> np.array([1,42]) in a
True
>>> np.array([42,1]) in a
False
4

5 に答える 5

62

.tolist()を使用できます

>>> a = np.array([[1,2],[10,20],[100,200]])
>>> [1,2] in a.tolist()
True
>>> [1,20] in a.tolist()
False
>>> [1,20] in a.tolist()
False
>>> [1,42] in a.tolist()
False
>>> [42,1] in a.tolist()
False

または、ビューを使用します。

>>> any((a[:]==[1,2]).all(1))
True
>>> any((a[:]==[1,20]).all(1))
False

または、numpyリストを生成します(非常に遅い可能性があります)。

any(([1,2] == x).all() for x in a)     # stops on first occurrence 

または、numpyロジック関数を使用します。

any(np.equal(a,[1,2]).all(1))

これらの時間を計る場合:

import numpy as np
import time

n=300000
a=np.arange(n*3).reshape(n,3)
b=a.tolist()

t1,t2,t3=a[n//100][0],a[n//2][0],a[-10][0]

tests=[ ('early hit',[t1, t1+1, t1+2]),
        ('middle hit',[t2,t2+1,t2+2]),
        ('late hit', [t3,t3+1,t3+2]),
        ('miss',[0,2,0])]

fmt='\t{:20}{:.5f} seconds and is {}'     

for test, tgt in tests:
    print('\n{}: {} in {:,} elements:'.format(test,tgt,n))

    name='view'
    t1=time.time()
    result=(a[...]==tgt).all(1).any()
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='python list'
    t1=time.time()
    result = True if tgt in b else False
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='gen over numpy'
    t1=time.time()
    result=any((tgt == x).all() for x in a)
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='logic equal'
    t1=time.time()
    np.equal(a,tgt).all(1).any()
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

ヒットまたはミスがわかります。numpyルーチンは配列を検索するのと同じ速度です。Pythonin演算子は、初期のヒットに対してはるかに高速である可能性があり、配列全体を調べなければならない場合、ジェネレーターは悪いニュースです。

300,000x3要素配列の結果は次のとおりです。

early hit: [9000, 9001, 9002] in 300,000 elements:
    view                0.01002 seconds and is True
    python list         0.00305 seconds and is True
    gen over numpy      0.06470 seconds and is True
    logic equal         0.00909 seconds and is True

middle hit: [450000, 450001, 450002] in 300,000 elements:
    view                0.00915 seconds and is True
    python list         0.15458 seconds and is True
    gen over numpy      3.24386 seconds and is True
    logic equal         0.00937 seconds and is True

late hit: [899970, 899971, 899972] in 300,000 elements:
    view                0.00936 seconds and is True
    python list         0.30604 seconds and is True
    gen over numpy      6.47660 seconds and is True
    logic equal         0.00965 seconds and is True

miss: [0, 2, 0] in 300,000 elements:
    view                0.00936 seconds and is False
    python list         0.01287 seconds and is False
    gen over numpy      6.49190 seconds and is False
    logic equal         0.00965 seconds and is False

そして、3,000,000 x 3アレイの場合:

early hit: [90000, 90001, 90002] in 3,000,000 elements:
    view                0.10128 seconds and is True
    python list         0.02982 seconds and is True
    gen over numpy      0.66057 seconds and is True
    logic equal         0.09128 seconds and is True

middle hit: [4500000, 4500001, 4500002] in 3,000,000 elements:
    view                0.09331 seconds and is True
    python list         1.48180 seconds and is True
    gen over numpy      32.69874 seconds and is True
    logic equal         0.09438 seconds and is True

late hit: [8999970, 8999971, 8999972] in 3,000,000 elements:
    view                0.09868 seconds and is True
    python list         3.01236 seconds and is True
    gen over numpy      65.15087 seconds and is True
    logic equal         0.09591 seconds and is True

miss: [0, 2, 0] in 3,000,000 elements:
    view                0.09588 seconds and is False
    python list         0.12904 seconds and is False
    gen over numpy      64.46789 seconds and is False
    logic equal         0.09671 seconds and is False

np.equalこれは、これを行うための最速の純粋なゴツゴツした方法であることを示しているようです...

于 2013-02-08T06:18:12.377 に答える
22

Numpys__contains__ は、これを書いている時点で(a == b).any()は、おそらくスカラーの場合にのみ正しいですb(少し毛深いですが、1.7以降でのみこのように機能すると思います-これは正しい一般的な方法(a == b).all(np.arange(a.ndim - b.ndim, a.ndim)).any()であり、ab次元のすべての組み合わせの意味)..

編集:明確にするために、これは放送が含まれる場合に必ずしも期待される結果ではありません。aまた、誰かが、アイテムを別々に処理する必要があると主張するかもしれませんnp.in1d。それが機能する明確な方法が1つあるかどうかはわかりません。

ここで、numpyが最初の出現を検出したときに停止する必要があります。このAFAIKは現時点では存在しません。numpyは主に配列全体で同じことを行うufuncsに基づいているため、困難です。Numpyはこれらの種類の削減を最適化しますが、効果的には、削減される配列がすでにブール配列(つまりnp.ones(10, dtype=bool).any())である場合にのみ機能します。

そうしないと__contains__、存在しない特別な関数が必要になります。それは奇妙に思えるかもしれませんが、numpyは多くのデータ型をサポートし、正しいものを選択してそれに取り組むための正しい関数を選択するためのより大きな機構を持っていることを覚えておく必要があります。つまり、ufunc機構はそれを実行できず__contains__、データ型のために、実装などを特別に行うことは実際にはそれほど簡単ではありません。

もちろんPythonで書くこともできますし、おそらくデータ型を知っているので、Cython/Cで自分で書くのはとても簡単です。


そうは言った。多くの場合、これらのことにはソートベースのアプローチを使用する方がはるかに優れています。searchsortedこれは少し面倒で、のようなものはありませんlexsortが、機能します(必要に応じて悪用することもできscipy.spatial.cKDTreeます)。これは、最後の軸に沿ってのみ比較することを前提としています。

# Unfortunatly you need to use structured arrays:
sorted = np.ascontiguousarray(a).view([('', a.dtype)] * a.shape[-1]).ravel()

# Actually at this point, you can also use np.in1d, if you already have many b
# then that is even better.

sorted.sort()

b_comp = np.ascontiguousarray(b).view(sorted.dtype)
ind = sorted.searchsorted(b_comp)

result = sorted[ind] == b_comp

これは配列に対しても機能しbます。並べ替えられた配列を保持する場合は、一度に1つの値(行)に対して同じままbで実行すると、はるかに優れていますa(そうでない場合はnp.in1d、再配列)。重要:np.ascontiguousarray安全のために行う必要があります。通常は何もしませんが、そうすると、そうでなければ大きな潜在的なバグになります。

于 2013-02-08T12:12:57.300 に答える
9

おもう

equal([1,2], a).all(axis=1)   # also,  ([1,2]==a).all(axis=1)
# array([ True, False, False], dtype=bool)

一致する行をリストします。Jamie が指摘しているように、そのような行が少なくとも 1 つ存在するかどうかを知るには、次を使用しますany

equal([1,2], a).all(axis=1).any()
# True

余談: (and
)は上記と同じですが、代わりに.in__contains__anyall

于 2013-02-08T05:30:05.910 に答える
1

本当に最初の発生で停止したい場合は、次のようなループを記述できます。

import numpy as np

needle = np.array([10, 20])
haystack = np.array([[1,2],[10,20],[100,200]])
found = False
for row in haystack:
    if np.all(row == needle):
        found = True
        break
print("Found: ", found)

ただし、配列全体に対してnumpyルーチンを使用して実行する他の提案よりもはるかに遅くなると強く思います。

于 2013-02-08T10:01:43.947 に答える