Reputation: 11922
I have a 3d numpy array build like this:
a = np.ones((3,3,3))
And I would like to broadcast values on all dimensions starting from a certain point with given coordinates, but the number of dimensions may vary.
For example if i'm given the coordinates (1,1,1)
I can do these 3 functions:
a[1,1,:] = 0
a[1,:,1] = 0
a[:,1,1] = 0
And the result will be my desired output which is:
array([[[1., 1., 1.],
[1., 0., 1.],
[1., 1., 1.]],
[[1., 0., 1.],
[0., 0., 0.],
[1., 0., 1.]],
[[1., 1., 1.],
[1., 0., 1.],
[1., 1., 1.]]])
Or if i'm given the coordinates (0,1,0)
the corresponding broadcast will be:
a[0,1,:] = 0
a[0,:,0] = 0
a[:,1,0] = 0
Is there any way to do this in a single action instead of 3? I'm asking because the actual arrays i'm working with have even more dimensions which makes the code seem long and redundant. Also if the number of dimensions change I would have to rewrite the code.
EDIT: It doesn't have to be a single action, I just need to do it in all dimensions programatically such that if the number of dimensions change the code will stay the same.
EDIT 2: About the logic of this, i'm not sure if that's relevant, but i'm being given the value of a point (by coordinates) on a map and based on that I know the values of the entire row, column and height on the same map (that's why i'm updating all 3 with 0
as an example). In other cases the map is 2-dimensions and I still know the same thing about the row and column, but can't figure out a function that works for a varied numbers of dimensions.
Upvotes: 3
Views: 388
Reputation: 25023
What you want to do can be done programmatically using a slice
object that is instantiated using the slice
function — e.g., see " Dealing with variable numbers of indices within programs".
def broadcast_val(mat, val, indices):
shape = mat.shape
for i in len(indices):
len_axis = shape[i]
i_axis = slice(len_axis)
index_tuple = *indices[:i], i_axis, *indices[i+1:]
mat[index_tuple] = val
I wonder if it is possible to do what you want using a single assignment...
Upvotes: 2
Reputation: 7984
This method builds the slices with np.repeat
and np.fill_diagonal
.
a = np.ones((3,3,3))
target = (0, 1, 0)
slices = np.repeat([target], a.ndim, axis=0).astype(object)
np.fill_diagonal(slices, slice(None))
# slices
# array([[slice(None, None, None), 1, 0],
# [0, slice(None, None, None), 0],
# [0, 1, slice(None, None, None)]], dtype=object)
for t in slices:
a[tuple(t)] = 0
array([[[0., 1., 1.],
[0., 0., 0.],
[0., 1., 1.]],
[[1., 1., 1.],
[0., 1., 1.],
[1., 1., 1.]],
[[1., 1., 1.],
[0., 1., 1.],
[1., 1., 1.]]])
np.repeat([target], a.ndim, axis=0)
repeats (0, 1, 0)
for a.ndim
times and it gives
array([[0, 1, 0],
[0, 1, 0],
[0, 1, 0]])
Then, we want to change part of each row to slice(None)
. We cannot do it directly as the type of slice
object is different from int
. Thus, we use astype(object)
to change the type of the array.
Upvotes: 2
Reputation: 401
Here's a way to generate string of exactly the 3 lines of code you're currently using, and then execute them:
import numpy as np
a = np.ones([3,3,3])
coord = [1, 1, 1]
for i in range(len(coord)):
temp = coord[:]
temp[i] = ':'
slice_str = ','.join(map(str, temp))
exec("a[%s] = 0"%slice_str)
print a
This may not be the best approach, but at least it's amusing. Now that we know that it works, we can go out and find the appropriate syntax to do it without actually generating the string and exec
ing it. For example, you could use slice
:
import numpy as np
a = np.ones([3,3,3])
coord = [1, 1, 1]
for i, length in enumerate(a.shape):
temp = coord[:]
temp[i] = slice(length)
a[temp] = 0
print a
Upvotes: 1