Давайте сначала сосредоточимся на использовании math.log
против np.log
:
In [132]: x = np.linspace(1,10,5)
Простая итерация списка:
In [133]: [math.log(i) for i in x]
Out[133]:
[0.0,
1.1786549963416462,
1.7047480922384253,
2.0476928433652555,
2.302585092994046]
Создание массива:
In [134]: np.array([math.log(i) for i in x])
Out[134]: array([0. , 1.178655 , 1.70474809, 2.04769284, 2.30258509])
Теперь с np.vectorize
:
In [135]: f = np.vectorize(np.log, otypes=[float])
In [136]: f(x)
Out[136]: array([0. , 1.178655 , 1.70474809, 2.04769284, 2.30258509])
И np.log
:
In [137]: np.log(x)
Out[137]: array([0. , 1.178655 , 1.70474809, 2.04769284, 2.30258509])
С этим небольшим x
тайминги будут похожи, но для гораздо большего массиваnp.log
явно выигрывает:
In [138]: xb = np.linspace(1,10,5000)
In [139]: timeit np.array([math.log(i) for i in xb])
1.28 ms ± 3.85 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [140]: timeit f(xb)
6.84 ms ± 250 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [141]: timeit np.log(xb)
174 µs ± 674 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Теперь попробуйте версию, которая может обрабатывать отрицательные значения и значения 0:
def foo(x):
if x==0:
return -np.inf
elif x<0:
return math.log(-x)
else:
return math.log(x)
Это можно использовать в цикле или vectorized
как указано выше.
Вместо простого np.log
мы можем разделить входные данные на разные блоки в зависимости от значения:
def foo1(x):
mask1 = x<0
mask2 = x>0
res = np.full_like(x, -np.inf)
res[mask1] = np.log(-x[mask1])
res[mask2] = np.log(x[mask2])
return res
Немного более изящная версия, использующая дополнительные параметры np.log
.Не волнуйтесь, если вы этого не понимаете.Это не намного быстрее и может оказаться бесполезным в ваших случаях.
def foo2(x):
mask1 = x<0
mask2 = x>0
res = np.full_like(x, -np.inf)
np.log(-x, out=res, where=mask1)
np.log(x, out=res, where=mask2)
return res
(тесты на равенство и временные параметры пока опущены).