1

How to use numpy apply_over_axes with lambda function?

This one:

import pandas as pd
import numpy as np

a = np.arange(6).reshape(2,3)

print(a)

>>[[0 1 2]
>> [3 4 5]]

def method_name(x,axis=0):
    return lambda x: print(x) or x ** 2
np.apply_over_axes(method_name, a,0)

results in:

AttributeError: 'function' object has no attribute 'ndim'

Ommiting any parameters in the declaration of method_name results in:

TypeError: method_name() takes 0 positional arguments but 2 were given
  • 1
    For a 2d array simply iterating on one axis is simpler and faster. And using a lambda for side effects like print is bad style even if it works. – hpaulj Aug 12 '18 at 14:05
  • thanks for that input. But just for my curiosity it would be a pleasure to understand the correct syntax :-) –  Aug 12 '18 at 14:16
  • 1
    I was thinking more of `apply_along_axis`. This `apply_over` is more obscure. – hpaulj Aug 12 '18 at 15:22
  • 1
    That's not a lambda function, it's a function returning a lambda function. just use `method_name = lambda x, axis: print(x) or x ** 2` – Eric Aug 13 '18 at 05:23
  • I think this comment would the best answer –  Aug 13 '18 at 06:58
  • 1
    `name=lambda ..,` is the format I use in my last 2 examples. – hpaulj Aug 13 '18 at 11:30
  • Yes, allright. But perhaps putting this information at the very beginning of your answer would make it a little bit faster to understand... :-) –  Aug 13 '18 at 11:40
  • https://stackoverflow.com/questions/52728493/numpy-vectorize-functions-apply-over-axes-apply-along-axis – cardamom Jan 11 '19 at 15:11

1 Answers1

2

I've helped with the other apply function more, so have had to look at the docs and experiment.

But look at what happens when we call your function:

In [185]: method_name(a,0)
Out[185]: <function __main__.method_name.<locals>.<lambda>(x)>

It returned the lambda function. apply expects the function call to return an array, hence the complaint that it doesn't have a ndim.

Change the function:

def method_name(x,axis=0):
    l = lambda x: print(x) or x ** 2
    return l(x)

Now it works:

In [187]: np.apply_over_axes(method_name, a,0)
[[0 1 2]
 [3 4 5]]
Out[187]: 
array([[ 0,  1,  4],
       [ 9, 16, 25]])

So it has called the method_name once, passing it a. Your lambda prints a (as a side effect) and returns its square.

What if we provide 2 axes values?

In [188]: np.apply_over_axes(method_name, a,[0,1])
[[0 1 2]
 [3 4 5]]
[[ 0  1  4]
 [ 9 16 25]]
Out[188]: 
array([[  0,   1,  16],
       [ 81, 256, 625]])

It's squared it twice! For the 2nd call it passed as x the result from the first call. The axes don't have to be unique or in order either: np.apply_over_axes(method_name, a,[0,1,0,1])

The docs illustrate it with np.sum as the function, and demonstrate the equivalence with sum using several axes. Originally functions like sum took just one axis value. In that case this apply_over_axes would have been a handy way of repeating the sum (instead of np.sum(np.sum(a, 0),0)). But now functions like that take a list, so there's less need for this apply_over_axes function.


cumsum might be a better illustration than sum, since it returns an array of the dimensions, and doesn't take multiple axes:

sum vertically:

In [210]: np.cumsum(a,0)
Out[210]: 
array([[0, 1, 2],
       [3, 5, 7]])

and again across columns:

In [211]: np.cumsum(_,1)
Out[211]: 
array([[ 0,  1,  3],
       [ 3,  8, 15]])

Doing the same thing with apply:

In [212]: np.apply_over_axes(np.cumsum, a, [0])
Out[212]: 
array([[0, 1, 2],
       [3, 5, 7]])

In [213]: np.apply_over_axes(np.cumsum, a, [0,1])
Out[213]: 
array([[ 0,  1,  3],
       [ 3,  8, 15]])

or another:

In [229]: l = lambda a, axis: np.add.accumulate(a**2, axis)

In [230]: l(l(a,0),1)
Out[230]: 
array([[   0,    1,   17],
       [  81,  370, 1211]])

In [231]: np.apply_over_axes(l, a, [0,1])
Out[231]: 
array([[   0,    1,   17],
       [  81,  370, 1211]])

It may be more useful for a case where the function reduces the dimensions, such as a reduce:

In [236]: rl = lambda a, axis: np.add.reduce(a**2, axis)

In [237]: np.apply_over_axes(rl, a, [0,1])
Out[237]: array([[1211]])

apply_over_axes does an better job of keeping track of dimensions than nested calls:

In [239]: rl(rl(a,0),0)
Out[239]: 1211
In [240]: rl(rl(a,0)[None,:],1)
Out[240]: array([1211])
hpaulj
  • 221,503
  • 14
  • 230
  • 353