私は次のようにできることを知っています:
import numpy as np
N=10
a=np.arange(1,100,1)
np.argsort()[-N:]
ただし、完全にソートされているため、非常に低速です。
私はnumpyがそれを速くするいくつかのメソッドを提供しているかどうか疑問に思います。
bottleneck
モジュールには、Numpy配列で直接機能する高速な部分ソート方法があります: bottleneck.partition()
。
bottleneck.partition()
はソートされた実際の値を返すことに注意してください。ソートされた値のインデックス(numpy.argsort()
が返すもの)が必要な場合は bottleneck.argpartition()
。
私はベンチマークしました:
z = -bottleneck.partition(-a, 10)[:10]
z = a.argsort()[-10:]
z = heapq.nlargest(10, a)
ここで、a
はランダムな1,000,000要素の配列です。
タイミングは次のとおりです。
bottleneck.partition()
:ループあたり25.6ミリ秒np.argsort()
:ループあたり198ミリ秒heapq.nlargest()
:ループあたり358ミリ秒numpy 1.8
は、部分ソートを実行するpartition
およびargpartition
を実装します(O(n)時間で、O(n) * log(n))。
import numpy as np
test = np.array([9,1,3,4,8,7,2,5,6,0])
temp = np.argpartition(-test, 4)
result_args = temp[:4]
temp = np.partition(-test, 4)
result = -temp[:4]
結果:
>>> result_args
array([0, 4, 8, 5]) # indices of highest vals
>>> result
array([9, 8, 6, 7]) # highest vals
タイミング:
In [16]: a = np.arange(10000)
In [17]: np.random.shuffle(a)
In [18]: %timeit np.argsort(a)
1000 loops, best of 3: 1.02 ms per loop
In [19]: %timeit np.argpartition(a, 100)
10000 loops, best of 3: 139 us per loop
In [20]: %timeit np.argpartition(a, 1000)
10000 loops, best of 3: 141 us per loop
提案されたボトルネックソリューションの各マイナス記号
-bottleneck.partsort(-a, 10)[:10]
データのコピーを作成します。コピーを削除することができます
bottleneck.partsort(a, a.size-10)[-10:]
また、提案されたnumpyソリューション
a.argsort()[-10:]
値ではなくインデックスを返します。修正方法は、インデックスを使用して値を見つけることです。
a[a.argsort()[-10:]]
2つのアプローチは異なるポイントでデータを分割するため、2つのボトルネックソリューションの相対速度は、初期配列の要素の順序に依存します。
言い換えれば、特定のランダム配列を使用してタイミングを調整すると、どちらの方法でも速く見えるようになります。
それぞれが1,000,000個の要素を持つ100個のランダム配列全体でタイミングを平均すると、
-bn.partsort(-a, 10)[:10]: 1.76 ms per loop
bn.partsort(a, a.size-10)[-10:]: 0.92 ms per loop
a[a.argsort()[-10:]]: 15.34 ms per loop
タイミングコードは次のとおりです。
import time
import numpy as np
import bottleneck as bn
def bottleneck_1(a):
return -bn.partsort(-a, 10)[:10]
def bottleneck_2(a):
return bn.partsort(a, a.size-10)[-10:]
def numpy(a):
return a[a.argsort()[-10:]]
def do_nothing(a):
return a
def benchmark(func, size=1000000, ntimes=100):
t1 = time.time()
for n in range(ntimes):
a = np.random.Rand(size)
func(a)
t2 = time.time()
ms_per_loop = 1000000 * (t2 - t1) / size
return ms_per_loop
t1 = benchmark(bottleneck_1)
t2 = benchmark(bottleneck_2)
t3 = benchmark(numpy)
t4 = benchmark(do_nothing)
print "-bn.partsort(-a, 10)[:10]: %0.2f ms per loop" % (t1 - t4)
print "bn.partsort(a, a.size-10)[-10:]: %0.2f ms per loop" % (t2 - t4)
print "a[a.argsort()[-10:]]: %0.2f ms per loop" % (t3 - t4)
この問題が発生しました。この質問は5歳なので、すべてのベンチマークをやり直してボトルネックの構文を変更する必要がありました(partsort
はもうありません。今はpartition
です)。
取得した要素の数を50に増やした以外は、kwgoodmanと同じ引数を使用しました(特定の状況に合わせて)。
私はこれらの結果を得ました:
bottleneck 1: 01.12 ms per loop
bottleneck 2: 00.95 ms per loop
pandas : 01.65 ms per loop
heapq : 08.61 ms per loop
numpy : 12.37 ms per loop
numpy 2 : 00.95 ms per loop
そのため、bottleneck_2とnumpy_2(adasのソリューション)は結び付けられました。ただし、np.percentile
(numpy_2)これらのtopN要素は既にソートされていますが、他のソリューションの場合はそうではありません。一方、これらの要素のインデックスにも関心がある場合、パーセンタイルは役に立ちません。
pandasも追加しました。これは、可能であれば、その下にボトルネックを使用します( http://pandas.pydata.org/pandas-docs/stable/install.html#recommended-dependencies )。既にpandas SeriesまたはDataFrameを持っている場合は、手始めにnlargest
を使用すれば完了です。
ベンチマークに使用されるコードは次のとおりです(python 3をご覧ください)。
import time
import numpy as np
import bottleneck as bn
import pandas as pd
import heapq
def bottleneck_1(a, n):
return -bn.partition(-a, n)[:n]
def bottleneck_2(a, n):
return bn.partition(a, a.size-n)[-n:]
def numpy(a, n):
return a[a.argsort()[-n:]]
def numpy_2(a, n):
M = a.shape[0]
perc = (np.arange(M-n,M)+1.0)/M*100
return np.percentile(a,perc)
def pandas(a, n):
return pd.Series(a).nlargest(n)
def hpq(a, n):
return heapq.nlargest(n, a)
def do_nothing(a, n):
return a[:n]
def benchmark(func, size=1000000, ntimes=100, topn=50):
t1 = time.time()
for n in range(ntimes):
a = np.random.Rand(size)
func(a, topn)
t2 = time.time()
ms_per_loop = 1000000 * (t2 - t1) / size
return ms_per_loop
t1 = benchmark(bottleneck_1)
t2 = benchmark(bottleneck_2)
t3 = benchmark(pandas)
t4 = benchmark(hpq)
t5 = benchmark(numpy)
t6 = benchmark(numpy_2)
t0 = benchmark(do_nothing)
print("bottleneck 1: {:05.2f} ms per loop".format(t1 - t0))
print("bottleneck 2: {:05.2f} ms per loop".format(t2 - t0))
print("pandas : {:05.2f} ms per loop".format(t3 - t0))
print("heapq : {:05.2f} ms per loop".format(t4 - t0))
print("numpy : {:05.2f} ms per loop".format(t5 - t0))
print("numpy 2 : {:05.2f} ms per loop".format(t6 - t0))
おそらくheapq.nlargest
import numpy as np
import heapq
x = np.array([1,-5,4,6,-3,3])
z = heapq.nlargest(3,x)
結果:
>>> z
[6, 4, 3]
n
を使用してbottleneck
の最大要素のインデックスを検索する場合は、bottleneck.argpartsort
>>> x = np.array([1,-5,4,6,-3,3])
>>> z = bottleneck.argpartsort(-x, 3)[:3]
>>> z
array([3, 2, 5]
Numpyのパーセンタイル関数を使用することもできます。私の場合、bottleneck.partsort()よりわずかに高速でした:
import timeit
import bottleneck as bn
N,M,K = 10,1000000,100
start = timeit.default_timer()
for k in range(K):
a=np.random.uniform(size=M)
tmp=-bn.partsort(-a, N)[:N]
stop = timeit.default_timer()
print (stop - start)/K
start = timeit.default_timer()
perc = (np.arange(M-N,M)+1.0)/M*100
for k in range(K):
a=np.random.uniform(size=M)
tmp=np.percentile(a,perc)
stop = timeit.default_timer()
print (stop - start)/K
ループあたりの平均時間:
配列を数値のリストとして保存しても問題がない場合は、次を使用できます。
import heapq
heapq.nlargest(N, a)
N
最大メンバーを取得します。