Как ускорить цикл в NumPy?

Я хотел бы ускорить этот код:

import numpy as np
import pandas as pd

a = pd.read_csv(path)
closep = a['Clsprc']
delta = np.array(closep.diff())
upgain = np.where(delta >= 0, delta, 0)
downloss = np.where(delta <= 0, -delta, 0)
up = sum(upgain[0:14]) / 14
down = sum(downloss[0:14]) / 14
u = []
d = []
for x in np.nditer(upgain[14:]):
    u1 = 13 * up + x
    u.append(u1)
    up = u1
for y in np.nditer(downloss[14:]):
    d1 = 13 * down + y
    d.append(d1)
    down = d1

Данные ниже:

0     49.00
1     48.76
2     48.52
3     48.28
...
36785758    13.88
36785759    14.65
36785760    13.19

Name: Clsprc, Length: 36785759, dtype: float64

Цикл for слишком медленный, что я могу сделать, чтобы ускорить этот код? Могу ли я векторизовать всю операцию?

2 ответа

Похоже, вы пытаетесь вычислить экспоненциальное скользящее среднее (скользящее среднее), но забыли деление. Если это так, то вы можете увидеть этот вопрос. Между тем, вот быстрая простая скользящая средняя, ​​использующая cumsum() функция взята по ссылочной ссылке.

def moving_average(a, n=14) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n - 1:] / n

Если это не так, и вы действительно хотите описанную функцию, вы можете увеличить скорость итерации, используя external_loop флаг в вашей итерации. Из обалденной документации:

Индитер будет пытаться предоставить куски как можно большего размера для внутреннего цикла. Формируя порядок 'C' и 'F', мы получаем разные размеры внешнего цикла. Этот режим включается указанием флага итератора.

Обратите внимание, что при сохранении собственного порядка памяти по умолчанию итератор может предоставить один одномерный фрагмент, тогда как при форсировании порядка Fortran он должен предоставлять три блока по два элемента каждый.

for x in np.nditer(upgain[14:],  flags=['external_loop'], order='F'):
    # x now has x[0],x[1], x[2], x[3], x[4], x[5] elements.

Проще говоря, я думаю, что это то, что делают циклы:

upgain=np.array([.1,.2,.3,.4])    
u=[]
up=1
for x in upgain:                  
    u1=10*up+x
    u.append(u1)
    up=u1

производство:

[10.1, 101.2, 1012.3, 10123.4]

np.cumprod([10,10,10,10]) есть плюс модифицированный cumsum для [.1,.2,.3,.4] термины. Но я не могу придумать, как объединить их с numpy функции. Мы могли бы написать обычай ufuncи использовать его accumulate, Или мы могли бы написать это в cython (или другой c интерфейс).

/questions/23193801/obobschennyie-kumulyativnyie-funktsii-v-numpyscipy/23193809#23193809 предполагает, что frompyfunc это способ написания обобщенного accumulate, Я не ожидаю большой экономии времени, может быть, в 2 раза.


Использовать frompyfuncопределить:

def foo(x,y):return 10*x+y

Приложение цикла (выше) будет

def loopfoo(upgain,u,u1):
    for x in upgain:
        u1=foo(u1,x)
        u.append(u1)
    return u

"Векторизованная" версия будет:

vfoo=np.frompyfunc(foo,2,1) # 2 in arg, 1 out
vfoo.accumulate(upgain,dtype=object).astype(float)

dtype=object требование было отмечено в предыдущем SO, и https://github.com/numpy/numpy/issues/4155

In [1195]: loopfoo([1,.1,.2,.3,.4],[],0)
Out[1195]: [1, 10.1, 101.2, 1012.3, 10123.4]

In [1196]: vfoo.accumulate([1,.1,.2,.3,.4],dtype=object)
Out[1196]: array([1.0, 10.1, 101.2, 1012.3, 10123.4], dtype=object)

Для этого небольшого списка, loopfoo быстрее (3 мкс против 21 мкс)

Для массива из 100 элементов, например biggain=np.linspace(.1,1,100), vfoo.accumulate быстрее:

In [1199]: timeit loopfoo(biggain,[],0)
1000 loops, best of 3: 281 µs per loop

In [1200]: timeit vfoo.accumulate(biggain,dtype=object)
10000 loops, best of 3: 57.4 µs per loop

Для еще большего biggain=np.linspace(.001,.01,1000) (меньшее число, чтобы избежать переполнения), сохраняется 5-кратное соотношение скоростей.

Другие вопросы по тегам