Быстрый способ найти наибольшие N элементов в массиве numpy
Я знаю, что могу сделать это следующим образом:
import numpy as np
N=10
a=np.arange(1,100,1)
np.argsort()[-N:]
Тем не менее, он очень медленный, так как он выполнял полный вид.
Интересно, обеспечивают ли numpy некоторые методы, чтобы сделать это быстро.
Ответы
Ответ 1
Модуль 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
- случайный массив из 1000 000 элементов.
Тайминги были следующими:
-
bottleneck.partition()
: 25,6 мс за цикл -
np.argsort()
: 198 мс за цикл -
heapq.nlargest()
: 358 мс за цикл
Ответ 2
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
Timing:
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
Ответ 3
Каждый отрицательный знак в предлагаемом решении для узких мест
-bottleneck.partsort(-a, 10)[:10]
создает копию данных. Мы можем удалить копии, выполнив
bottleneck.partsort(a, a.size-10)[-10:]
Также предложенное решение numpy
a.argsort()[-10:]
возвращает индексы не значения. Исправление состоит в том, чтобы использовать индексы для поиска значений:
a[a.argsort()[-10:]]
Относительная скорость двух решений узких мест зависит от упорядочения элементов в исходном массиве, поскольку оба подхода разделяют данные в разных точках.
Другими словами, синхронизация с любым конкретным случайным массивом может заставить любой метод выглядеть быстрее.
Усреднение времени по 100 случайным массивам, каждый с 1 000 000 элементов, дает
-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)
Ответ 4
Возможно 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]
Ответ 5
У меня была эта проблема, и, поскольку этому вопросу 5 лет, мне пришлось переделать все тесты и изменить синтаксис узкого места (больше нет partsort
, теперь он partition
).
Я использовал те же аргументы, что и kwgoodman, за исключением числа извлеченных элементов, которое я увеличил до 50 (чтобы лучше соответствовать моей конкретной ситуации).
Я получил следующие результаты:
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, что не относится к другим решениям. С другой стороны, если вас интересуют индексы этих элементов, процентиль не является полезным.
Я также добавил панд, который использует узкое место внизу, если доступно (http://pandas.pydata.org/pandas-docs/stable/install.html#recommended-dependencies). Если у вас уже есть серия pandas или 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))
Ответ 6
Вы также можете использовать функцию numpy percentile. В моем случае это было немного быстрее, чем 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
Среднее время на цикл:
- bottleneck.partsort(): 59 мс
- np.percentile(): 54 мс
Ответ 7
Если сохранение массива в виде списка чисел не является проблематичным, вы можете использовать
import heapq
heapq.nlargest(N, a)
чтобы получить наибольшие члены N
.