py_newbie
py_newbie

Reputation: 317

lambda function on a numpy array. What's wrong with this piece of code?

What's wrong with this code :

import numpy as np

A = np.array([[-0.5, 0.2, 0.0],
          [4.2, 3.14, -2.7]])

asign = lambda t: 0 if t<0 else 1
asign(A)
print(A)

expected out:

     [[0.  1.  0.]
      [ 1.  1. 0.]]

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Upvotes: 4

Views: 7934

Answers (5)

Rhubarb
Rhubarb

Reputation: 35833

Answering my own follow-on question which sort of answers the original. To recap / generalize:

The OP is asking "Why is the lambda applied to the numpy array as a whole object when I expected it to be applied element-wise?"

My followup asks "Why are some lambda applied as a whole while others are applied element_wise?"

The TL;DR answer is that the lambda always treats the numpy array as a whole object - a regular argument to a regular function - but the operator used inside the body of the lambda (or function, or wherever) may be overridden by numpy ndarray to work element-wise, and the == operator is such and operator.

In my example it's the == operator. I tracked down the override for this and unfortunately the official numpy documentation of the override is not much help:

numpy.ndarray.eq method

ndarray.eq(value, /) Return self==value.

(fwiw, I know this is the documenation for == because the equivalency of operators to special method names is defined in this python reference)

The correct answer required more digging - I found it in the numpy documentation of the numpy.equal function:

numpy.equal numpy.equal(x1, x2, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj]) = <ufunc 'equal'>

Return (x1 == x2) element-wise.

The == operator is applied element-wise!

Hence in my first lambda lambda x: x == "ccc", x does indeed hold the entire ndarray, but the == is applied to each element returning a new ndarray

Again the numpy.equal doc makes this clear:

Returns: out: ndarray or scalar

Output array, element-wise comparison of x1 and x2. Typically of type bool, unless dtype=object is passed. This is a scalar if both x1 and x2 are scalars.

x1 and x2 are the args, so that we're comparing x1 == x2. In our case, x1 is x so the full ndarray - not scalar - so the result is an ndarray.

You may be wondering why how it treats "ccc" (which is assigned to the x2 parameter), the doc says:

Parameters

x1, x2 array_like

Input arrays. If x1.shape != x2.shape, they must be broadcastable to a common shape (which becomes the shape of the output).

So x2 (our "ccc") is supposed to be array_like, but numpy will, if it can, broadcast it to the same shape as x1. And it will, because it can, as is documented in Numpy Broadcasting:

The simplest broadcasting example occurs when an array and a scalar value are combined in an operation...

The result is equivalent to the previous example where b was an array. We can think of the scalar b being stretched during the arithmetic operation into an array with the same shape as a.

QED.

Upvotes: 0

Rhubarb
Rhubarb

Reputation: 35833

Not an answer, sorry, but more data. I also can't find the explanation for the fact that some functions/lambdas act over the array, while the others treat it as a whole.

See this testing:

string_arr = [ "a", "bb", "ccc", "dddd" ]
ndstr_arr = np.array(string_arr)
l1 = lambda x: x == "ccc"
l2 = lambda x: len(x) > 2

print("\nDirect lambda over array:")
print (l1(string_arr)) # fails
print (l2(string_arr)) # fails
print (l1(ndstr_arr))  # WORKS
print (l2(ndstr_arr))  # fails

print("\nList(map(lambda over array)): ")
print (list(map(l1,string_arr))) # WORKS
print (list(map(l2,string_arr))) # WORKS
print (list(map(l1,ndstr_arr)))  # WORKS
print (list(map(l2,ndstr_arr)))  # WORKS

for this result:

Direct lambda over array:
False
True
[False False  True False]
True

List(map(lambda over array)): 
[False, False, True, False]
[False, False, True, True]
[False, False, True, False]
[False, False, True, True]

Both lambda's are boolean functions, but for some reason the first one x==... gets mapped over the array (and note: only over the ndarray - the regular list, string_arr is never mapped) whereas len(x) > 2 acts on the array as single object. What's the difference between these lambdas?

(Also note that list(map) is not a real substitute since it doesn't return an ndarray, so we have to use it to build a new ndarray or use vectorize or some other method... that's not really the point here though)

Upvotes: 0

Khepu
Khepu

Reputation: 310

Well the lambda on its own will not go through the whole array. For that you will need a higher order function. In this case: map.

A = np.array([[-0.5, 0.2, 0.0],
              [4.2, 3.14, -2.7]])

asign = lambda t: 0 if t<0 else 1
A = list(map(asign, A))

Map will iterate through every element and pass it through the function. I wrapped map in a list because it returns an object of type map but you can convert it that way.

Upvotes: 5

Sigve Karolius
Sigve Karolius

Reputation: 1456

You can use the lambda, but the numpy datatypes allow you to do many "matlab-type" operations (for those who are used to that):

  • python:

    a = np.array([1, 2, 3, 4, 5])
    ((a > 1) & (a < 3)).astype(int)
    # array([0, 1, 0, 0, 0])
    
  • octave/matlab

    a = [1,2,3,4,5];
    a>1 & a<3
    % ans =
    %
    %  0  1  0  0  0
    

Upvotes: 1

py_newbie
py_newbie

Reputation: 317

This worked for me:

A = A.clip(min=0, max=1)

Upvotes: 0

Related Questions