Understanding NumPy's copy behaviour when using advanced indexing

Viewed 67

I am currently struggling writing tidy code in NumPy using advanced indexing.

arr = np.arange(100).reshape(10,10) # array I want to manipulate
sl1 = arr[:,-1] # basic indexing
# Do stuff with sl1...
sl1[:] = -1
# arr has been changed as well
sl2 = arr[arr >= 50] # advanced indexing
# Do stuff with sl2...
sl2[:] = -2
# arr has not been changed, 
# changes must be written back into it
arr[arr >= 50] = sl2 # What I'd like to avoid

I'd like to avoid this "write back" operation because it feels superfluous and I often forget it. Is there a more elegant way to accomplish the same thing?

1 Answers

Both boolean and integer array indexing, fall under the category of advanced indexing methods. In the second example (boolean indexing), you'll see that the original array is not updated, this is because advanced indexing always returns a copy of the data (see second paragraph in the advanced indexing section of the docs). This means that once you do arr[arr >= 50] this is already a copy of arr, and whatever changes you apply over it they won't affect arr.

The reason why it does not return a view is that advanced indexing cannot be expressed as a slice, and hence cannot be addressed with offsets, strides, and counts, which is required to be able to take a view of the array's elements.

We can easily verify that we are viewing different objects in the case of advanced indexing with:

np.shares_memory(arr, arr[arr>50])
# False
np.shares_memory(arr, arr[:,-1])
# True

Views are only returned when performing basic slicing operations. So you'll have to assign back as you're doing in the last example. In reference to the question in the comments, when assigning back in a same expression:

arr[arr >= 50] = -2

This is translated by the python interpreter as:

arr.__setitem__(arr >= 50, -2)

Here the thing to understand is that the expression can be evaluated in-place, hence there's no new object creation involved since there is no need for it.

Related