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

Question:

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()

Asked By: py_newbie

||

Answers:

This worked for me:

A = A.clip(min=0, max=1)
Answered By: py_newbie

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.

Answered By: Khepu

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
    
Answered By: Sigve Karolius

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)

Answered By: Rhubarb

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.

Answered By: Rhubarb
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.