Reputation: 2087
I would like to apply a function to each row of a 5x1 numpy array. The function I am applying takes two arguments, and I would like the elements of the array to enter as the second argument, not the first. I have been using numpy.apply_along_axis()
. Looking at the documentation for this function, it doesn't seem like it's possible.
Is there anyway to do this without explicitly defining a new function with the argument order reversed? I'm just curious. Here's the example I've been messing around with.
import numpy as np
tmp = np.random.rand(5,1)
lol = lambda first, second: float(first)/second
print tmp
np.apply_along_axis(lol, 1, tmp, second=1) #works but I don't want this
np.apply_along_axis(lol, 1, tmp, first=1) # doesn't work
Upvotes: 1
Views: 2482
Reputation: 152657
It is possible! But the function itself is called directly with the data being the first argument (see source), so you need another layer of abstraction to change the arguments: for example with a decorator:
from functools import wraps
def swapelements(func):
@wraps(func)
def wrapper(array, *args, **kwargs):
# Insert the array as second=array:
kwargs['second'] = array
return func(*args, **kwargs)
return wrapper
np.apply_along_axis(swapelements(lol), 1, tmp, first=1)
array([[ 6.24582532], [ 1.14800413], [ 1.87634432], [ 14.31209963], [ 2.47123623]])
with tmp
being [[ 0.16010694], [ 0.871077 ], [ 0.53295122], [ 0.06987095], [ 0.40465577]]
If you operate on a 5x1
array why do you use apply_along_axis
? You could achieve the same by just using:
result = 1. / tmp
Your second axis only contains one element.
Upvotes: 0
Reputation: 231395
I'm going to tweak your example to make it a bit more interesting
In [188]: tmp=np.arange(6.).reshape(2,3)
In [189]: lol=lambda first, second: np.sum(first)/second
So with 2d tmp we can apply it along either axis
In [190]: np.apply_along_axis(lol,0,tmp,2)
Out[190]: array([ 1.5, 2.5, 3.5])
In [191]: np.apply_along_axis(lol,1,tmp,2)
Out[191]: array([ 1.5, 6. ])
Another lambda
cleanly switches the arguments:
In [192]: np.apply_along_axis(lambda x,y:lol(y,x),0,tmp,2)
Out[192]:
array([[ inf, 2. , 1. ],
[ 0.66666667, 0.5 , 0.4 ]])
(same as 2/tmp
; axis doesn't matter)
The extra layer of function call doesn't change execution speed much (switching the arguments increases time, but I want to focus on the effect of the extra lambda
).
In [195]: timeit np.apply_along_axis(lol,1,tmp,2)
10000 loops, best of 3: 103 us per loop
In [196]: timeit np.apply_along_axis(lambda x,y:lol(x,y),1,tmp,2)
10000 loops, best of 3: 105 us per loop
There's nothing magical or extra efficient about using apply_along_axis
; you (or we) could write an iteration that does the same thing.
Let's compare 2 expressions, an apply_along_axis
and an equivalent list comprehension:
In [213]: np.apply_along_axis(lol,0,tmp,2)
Out[213]: array([ 1.5, 2.5, 3.5])
In [214]: np.array([lol(tmp[:,i],2) for i in range(tmp.shape[1])])
Out[214]: array([ 1.5, 2.5, 3.5])
In [215]: timeit np.apply_along_axis(lol,0,tmp,2)
10000 loops, best of 3: 132 us per loop
In [217]: timeit np.array([lol(tmp[:,i],2) for i in range(tmp.shape[1])])
10000 loops, best of 3: 64.1 us per loop
apply_along_axis
is slower, probably because it is trying to be more general. It may be more convenient, but it isn't obviously more 'efficient'.
Upvotes: 2