2

I am trying to make a zeroed array with the same shape of a source array. Then modify every value in the second array that corresponds to a specific value in the first array.

This would be simple enough if I was just replacing one value. Here is a toy example:

import numpy as np

arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])

arr2[arr1==1] = -1

This will work as expected and arr2 would be:

[[-1,0,0],
 [ 0,0,0],
 [-1,0,0]]

But I would like to replace an entire row. Something like this replacing the last line of the sample code above:

arr2[arr1==[3,4,5]] = [-1,-1,-1]

When I do this, it also works as expected and arr2 would be:

[[ 0, 0, 0],
 [-1,-1,-1],
 [ 0, 0, 0]]

But when I tried to replace the last line of sample code with something like:

arr2[arr1==[1,2,3]] = [-1,-1,-1]

I expected to get something like the last output, but with the 0th and 2nd rows being changed. But instead I got the following error.

ValueError: NumPy boolean array indexing assignment cannot assign 3 input values to the 6 
output values where the mask is true

I assume this is because, unlike the other example, it was going to have to replace more than one row. Though this seems odd to me, since it worked fine replacing more than one value in the simple single value example.

I'm just wondering if anyone can explain this behavior to me, because it is a little confusing. I am not that experienced with the inner workings of numpy operations. Also, if anyone has an any recommendations to do what I am trying to accomplish in an efficient manner.

In my real world implementation, I am working with a very large three dimensional array (an image with 3 color channels) and I want to make an new array that stores a specific value into these three color channels if the source image has a specific three color values in that corresponding pixel (and remain [0,0,0] if it doesn't match our pixel_rgb_of_interest). I could go through in linear time and just check every single pixel, but this could get kind of slow if there are a lot of images, and was just wondering if there was a better way.

Thank you!

2 Answers2

1

This would be a good application for numpy.where

>>> import numpy as np
>>> arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
>>> arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])
>>> np.where(arr1 == [1,2,3], [-1,-1,-1], arr1)
array([[-1, -1, -1],
       [ 3,  4,  5],
       [-1, -1, -1]])

This basically works as "wherever the condition is true, use the x argument, then use the y argument the rest of the time"

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • Thank you! I feel pretty dumb for not thinking about this. I started overthinking it and forgot all the np functions at my disposal. – SeismicSandwhich Aug 09 '21 at 19:48
  • @SeismicSandwhich No worries, numpy is incredibly powerful and useful, but doesn't necessarily win awards for discoverability. Can't knock their docs though, very well documented library once you find what you are actually looking for. – Cory Kramer Aug 09 '21 at 19:49
  • This seems to be having an issue with partial matches. For example. In your example, if you replaced arr==[1,2,3] with arr==[1,3,4] it would ouput: [[-1, 2, 3], [ 3, 4, 5], [-1, 2, 3]]. Is there any way to ensure that it has to make a full match in order to replace? I tried plugging in things like a.any() and a.all() but they didn't seem to do the trick. – SeismicSandwhich Aug 09 '21 at 20:47
  • 1
    Nvm, I think I needed to modify it a bit. Now I am trying to do something like this: arr2[(arr1==[1,2,3]).all(axis=1).nonzero()] = [-1,-1,-1], since I saw it in this post https://stackoverflow.com/questions/25823608/find-matching-rows-in-2-dimensional-numpy-array. It also mentions that it might be faster to test for each channel separately. Note, for a 3D application this would have to be axis=2 – SeismicSandwhich Aug 09 '21 at 21:16
0

Lets add an "index" array:

In [56]: arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
    ...: arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])
    ...: arr3 = np.arange(9).reshape(3,3)

The test against 1 value:

In [57]: arr1==1
Out[57]: 
array([[ True, False, False],
       [False, False, False],
       [ True, False, False]])

that has 2 true values:

In [58]: arr3[arr1==1]
Out[58]: array([0, 6])

We could assign one value as you do, or 2.

Test with a list, which is converted to array first:

In [59]: arr1==[3,4,5]
Out[59]: 
array([[False, False, False],
       [ True,  True,  True],
       [False, False, False]])

That has 3 True:

In [60]: arr3[arr1==[3,4,5]]
Out[60]: array([3, 4, 5])

so it works to assign a list of 3 values as you do. Or a scalar.

In [61]: arr1==[1,2,3]
Out[61]: 
array([[ True,  True,  True],
       [False, False, False],
       [ True,  True,  True]])

Here the test has 6 True.

In [62]: arr3[arr1==[1,2,3]]
Out[62]: array([0, 1, 2, 6, 7, 8])

So we can assign 6 values or a scalar. But you tried to assign 3 values.

Or we could apply all to find the rows that match [1,2,3]:

In [63]: np.all(arr1==[1,2,3], axis=1)
Out[63]: array([ True, False,  True])
In [64]: arr3[np.all(arr1==[1,2,3], axis=1)]
Out[64]: 
array([[0, 1, 2],
       [6, 7, 8]])

To this we could assign a (2,3) array, a scalar, a (3,) array, or a (2,1) (as per broadcasting rules):

In [65]: arr2[np.all(arr1==[1,2,3], axis=1)]=np.array([100,200])[:,None]
In [66]: arr2
Out[66]: 
array([[100, 100, 100],
       [  0,   0,   0],
       [200, 200, 200]])
hpaulj
  • 221,503
  • 14
  • 230
  • 353