Taylor
Taylor

Reputation: 2087

argument order in numpy.apply_along_axis

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

Answers (2)

MSeifert
MSeifert

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

hpaulj
hpaulj

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

Related Questions