NumPy Array Indexing and Replacing

2019-08-07 15:55发布

问题:

I have a 3d numpy array as follows:

(3L, 5L, 5L)

If one element in 3d positions, for instance, [150, 160, 170] exists. How can I convert all of them into [0,0,0]?

import numpy as np
a = np.ones((3,5,5))
a[0,2:4,2:4] = 150
a[0,0:1,0:1] = 150 #important!
a[1,2:4,2:4] = 160
a[2,2:4,2:4] = 170
print a

The expected result should be:

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

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

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

回答1:

First I would convert into a stack of triples:

b = np.reshape(a.transpose(2, 1, 0), [25,3])

Then find the values you want:

idx = np.where((b == np.array([150, 160, 170])).all(axis=1))

And replace with whatever value you want:

b[idx] = 0

And finally convert back to the original shape:

c = np.reshape(b, [5, 5, 3]).transpose(2, 1, 0) 


回答2:

Construct your a:

In [48]: a=np.ones((3,5,5))    
In [49]: a[0,2:4,2:4]=150
In [50]: a[1,2:4,2:4]=160
In [51]: a[2,2:4,2:4]=170

In [52]: a
Out[52]: 
array([[[   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,  150.,  150.,    1.],
        [   1.,    1.,  150.,  150.,    1.],
        [   1.,    1.,    1.,    1.,    1.]],

       [[   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,  160.,  160.,    1.],
        [   1.,    1.,  160.,  160.,    1.],
        [   1.,    1.,    1.,    1.,    1.]],

       [[   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,    1.,    1.,    1.],
        [   1.,    1.,  170.,  170.,    1.],
        [   1.,    1.,  170.,  170.,    1.],
        [   1.,    1.,    1.,    1.,    1.]]])

Boolean of all places where the values, on the 1st dimension, are [150,160,170]. The key is to expand this to a 3d, shape (3,1,1) that can be broadcast to (3,5,5) and compared with a:

In [53]: I = a==np.array([150,160,170])[:,None,None]

In [54]: I
Out[54]: 
array([[[False, False, False, False, False],
        [False, False, False, False, False],
        [False, False,  True,  True, False],
        [False, False,  True,  True, False],
        [False, False, False, False, False]],

       [[False, False, False, False, False],
        [False, False, False, False, False],
        [False, False,  True,  True, False],
        [False, False,  True,  True, False],
        [False, False, False, False, False]],

       [[False, False, False, False, False],
        [False, False, False, False, False],
        [False, False,  True,  True, False],
        [False, False,  True,  True, False],
        [False, False, False, False, False]]], dtype=bool)

Now it's trivial to change all those slots with value True to 0:

In [55]: a[I]=0

In [56]: a
Out[56]: 
array([[[ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  0.,  0.,  1.],
        [ 1.,  1.,  0.,  0.,  1.],
        [ 1.,  1.,  1.,  1.,  1.]],

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

       [[ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  0.,  0.,  1.],
        [ 1.,  1.,  0.,  0.,  1.],
        [ 1.,  1.,  1.,  1.,  1.]]])

Looking at the comments on a deleted answer I see you really want a case where all 3 values match. That is, contrary to your example, there may be other slots a[0,...] is 150, etc. that you don't want to change.

You could still work with this I, by just taking an all on the 1st axis:

In [58]: a[:,I.all(axis=0)]=2

In [59]: a
Out[59]: 
array([[[ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  1.,  1.,  1.]],

       [[ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  1.,  1.,  1.]],

       [[ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  2.,  2.,  1.],
        [ 1.,  1.,  1.,  1.,  1.]]])

Conceptually my answer is not that different from the accepted one. That reshapes the array into a 2d, so the matching can be done with a 1d [150,160,170]. Actually it is automatically reshaped to (1,3) which can be broadcast agains (25,3) array. Transposing and reshaping is a convenient way to standardize the format of problems like this, though it often isn't necessary.


It may help to split that last action up:

In [60]: J=I.all(axis=0)

In [61]: J
Out[61]: 
array([[False, False, False, False, False],
       [False, False, False, False, False],
       [False, False,  True,  True, False],
       [False, False,  True,  True, False],
       [False, False, False, False, False]], dtype=bool)

In [62]: a[:, J] = 3

or use np.where to convert the boolean array to 2 list of indices:

In [73]: jj=np.where(J)
In [74]: jj
Out[74]: (array([2, 2, 3, 3], dtype=int32), array([2, 3, 2, 3], dtype=int32))

In [75]: a[:, jj[0], jj[1]] = 4

a[:,jj] doesn't work, but a[(slice(None),)+jj] does. This last expression constructs a 3 element tuple, the equivalent to [75]'s.

I could also expand J to a size and shape that matches a:

In [90]: K=J[None,...].repeat(3,0)
In [91]: K
Out[91]: 
array([[[False, False, False, False, False],
        [False, False, False, False, False],
        [False, False,  True,  True, False],
        [False, False,  True,  True, False],
        [False, False, False, False, False]],

       [[False, False, False, False, False],
        ....]], dtype=bool)

and use a[K].