1

I'm working on a code and a question just pop up in my head. So basically I have a 3D numpy array with shape
(2, 5, 5) and also a 2D numpy array with shape (2, 4) (this is just an example, the arrays, can be much bigger). What I need is to replace the values of 1 in the subarrays of the 3D array (slice [:, 2:, 2:] ) by the values in my 2D array. I thought about getting the index from the values I want to change (the ones) in the 3D array and then use a for loop in the 2D array to iterate through the values but I'm not sure if it's efficient way and also I'm getting an error.

import numpy as np

arr = np.array([[[0.,  1., 43., 25., 21.],
                 [0.,  0.,  0.,  0.,  0.],
                 [0., 43.,  0.,  1.,  0.], 
                 [0., 43.,  1.,  0.,  1.],
                 [0., 45.,  0.,  1.,  0.]],

                [[0.,  1., 38., 29., 46.],
                 [0.,  0.,  0.,  0.,  0.],
                 [0., 32.,  0.,  0.,  1.],
                 [0., 26.,  0.,  0.,  1.],
                 [0., 30.,  1.,  1.,  0.]]])

values = [[2, 3, 1, 4],
          [4, 1, 5, 9]]

indexes = np.argwhere(newarr[:, 2:, 2:] == 1) + [0, 2, 2]

# indexes = [[0 2 3]
#            [0 3 2]
#            [0 3 4]
#            [0 4 3]
#            [1 2 4]
#            [1 3 4]
#            [1 4 2]
#            [1 4 3]]

for i in values:
    arr[indexes] == i

#Error
#index 2 is out of bounds for axis 0 with size 2

My desired output should be

newarr = [[[0.,  1., 43., 25., 21.],
           [0.,  0.,  0.,  0.,  0.],
           [0., 43.,  0.,  2.,  0.], 
           [0., 43.,  3.,  0.,  1.],
           [0., 45.,  0.,  4.,  0.]],

          [[0.,  1., 38., 29., 46.],            
           [0.,  0.,  0.,  0.,  0.],
           [0., 32.,  0.,  0.,  4.],
           [0., 26.,  0.,  0.,  1.],
           [0., 30.,  5.,  9.,  0.]]])

I think there should be a more efficient using only numpy, but I can't see how to do this, so any help will be appreciated, thank you!

2 Answers 2

2

You can slice arr and use the actual binary values cast to bool to mask the array and slice assign where there are 1s:

a_view = arr[:,2:,2:]
a_view[a_view.astype('bool')] = np.array(values).ravel()

print(arr)
array([[[ 0.,  1., 43., 25., 21.],
        [ 0.,  0.,  0.,  0.,  0.],
        [ 0., 43.,  0.,  2.,  0.],
        [ 0., 43.,  3.,  0.,  1.],
        [ 0., 45.,  0.,  4.,  0.]],

       [[ 0.,  1., 38., 29., 46.],
        [ 0.,  0.,  0.,  0.,  0.],
        [ 0., 32.,  0.,  0.,  4.],
        [ 0., 26.,  0.,  0.,  1.],
        [ 0., 30.,  5.,  9.,  0.]]])
Sign up to request clarification or add additional context in comments.

Comments

2

Two issues:

  • use np.where instead of argwhere; you won't need to iterate
  • the resulting indices apply to the slice, not the original array

With your array:

In [133]: arr = np.array([[[0.,  1., 43., 25., 21.], 
     ...:                  [0.,  0.,  0.,  0.,  0.], 
          ...
     ...:                  [0., 26.,  0.,  0.,  1.], 
     ...:                  [0., 30.,  1.,  1.,  0.]]]) 
     ...:  

the slice:

In [136]: subarr = arr[:,2:,2:]                                                                      

where the slice values are 1:

In [137]: np.nonzero(subarr==1)                                                                      
Out[137]: 
(array([0, 0, 0, 0, 1, 1, 1, 1]),
 array([0, 1, 1, 2, 0, 1, 2, 2]),
 array([1, 0, 2, 1, 2, 2, 0, 1]))

This like your argwhere except as a tuple of arrays, and not offset.

In [138]: values = [[2, 3, 1, 4], 
     ...:           [4, 1, 5, 9]] 
     ...:                                     

That tuple can be used to index the slice, both for fetching and setting:

In [139]: subarr[np.nonzero(subarr==1)]                                                              
Out[139]: array([1., 1., 1., 1., 1., 1., 1., 1.])
In [140]: subarr[np.nonzero(subarr==1)]=np.ravel(values)             

Since subarr is a view, setting values in it also sets values in arr. No need to convert the indices to the arr framework.

In [141]: arr                                                                                        
Out[141]: 
array([[[ 0.,  1., 43., 25., 21.],
        [ 0.,  0.,  0.,  0.,  0.],
        [ 0., 43.,  0.,  2.,  0.],
        [ 0., 43.,  3.,  0.,  1.],
        [ 0., 45.,  0.,  4.,  0.]],

       [[ 0.,  1., 38., 29., 46.],
        [ 0.,  0.,  0.,  0.,  0.],
        [ 0., 32.,  0.,  0.,  4.],
        [ 0., 26.,  0.,  0.,  1.],
        [ 0., 30.,  5.,  9.,  0.]]])

boolean indexing

As the other answer notes, we can select elements with the boolean mask, without the nonzero step (under the covers the indexing is similar if not identical).

In [144]: arr = np.array([[[0.,  1., 43., 25., 21.], 
     ...:                  [0.,  0.,  0.,  0.,  0.], 
    ...
     ...:                  [0., 30.,  1.,  1.,  0.]]]) 
     ...:                                                                                            
In [145]: subarr = arr[:,2:,2:]                                                                      
In [146]: subarr[subarr==1]                                                                          
Out[146]: array([1., 1., 1., 1., 1., 1., 1., 1.])

In [148]: subarr[subarr==1] = np.ravel(values)                                                       
In [149]: arr                                                                                        
Out[149]: 
array([[[ 0.,  1., 43., 25., 21.],
        [ 0.,  0.,  0.,  0.,  0.],
        ....
        [ 0., 30.,  5.,  9.,  0.]]])

closer to your attempt

Actually your iteration could have worked if you'd iterated on indices:

for i,v in zip(idx,np.ravel(values)):
     arr[tuple(i)] == v

Starting with my where tuple:

In [159]: Out[137]                                                                                   
Out[159]: 
(array([0, 0, 0, 0, 1, 1, 1, 1]),
 array([0, 1, 1, 2, 0, 1, 2, 2]),
 array([1, 0, 2, 1, 2, 2, 0, 1]))
In [160]:                                                                                            

Your offset argwhere:

In [160]: idx = np.transpose(Out[137])+[0,2,2]                                                       
In [161]: idx                                                                                        
Out[161]: 
array([[0, 2, 3],
       [0, 3, 2],
       [0, 3, 4],
       [0, 4, 3],
       [1, 2, 4],
       [1, 3, 4],
       [1, 4, 2],
       [1, 4, 3]])

Using that iteratively to index arr (note the use of tuple):

In [162]: [arr[tuple(i)] for i in idx]                                                               
Out[162]: [2.0, 3.0, 1.0, 4.0, 4.0, 1.0, 5.0, 9.0]

Iterating directly on values doesn't work, since that returns 2 lists. It needs to be flattened/raveled.

In [163]: for v in values: print(v)                                                                  
[2, 3, 1, 4]
[4, 1, 5, 9]

1 Comment

Thank you for the long answer, I can see a lot of things I was doing wrong, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.