7

Perhaps this has been raised and addressed somewhere else but I haven't found it. Suppose we have a numpy array:

a = np.arange(100).reshape(10,10)
b = np.zeros(a.shape)
start = np.array([1,4,7])   # can be arbitrary but valid values
end = np.array([3,6,9])     # can be arbitrary but valid values

start and end both have valid values so that each slicing is also valid for a. I wanted to copy value of subarrays in a to corresponding spots in in b:

b[:, start:end] = a[:, start:end]   #error

this syntax doesn't work, but it's equivalent to:

b[:, start[0]:end[0]] = a[:, start[0]:end[0]]
b[:, start[1]:end[1]] = a[:, start[1]:end[1]]
b[:, start[2]:end[2]] = a[:, start[2]:end[2]]

I wonder if there is a better way of doing this instead of an explicit for-loop over the start and end arrays.

Thanks!

3
  • Would the start and end pairs always have a constant difference (2 here)? Commented Oct 13, 2017 at 16:20
  • not necessarily, this is just an example, but can assume the indices are valid ones to index with Commented Oct 13, 2017 at 16:22
  • Does this answer your question? How to slice numpy rows using start and end index Commented Oct 23, 2021 at 13:13

1 Answer 1

6

We can use broadcasting to create a mask of places to be edited with two sets of comparisons against start and end arrays and then simply assign with boolean-indexing for a vectorized solution -

# Range array for the length of columns
r = np.arange(b.shape[1])

# Broadcasting magic to give us the mask of places
mask = (start[:,None] <= r) & (end[:,None] >= r)

# Boolean-index to select and assign 
b[:len(mask)][mask] = a[:len(mask)][mask]

Sample run -

In [222]: a = np.arange(50).reshape(5,10)
     ...: b = np.zeros(a.shape,dtype=int)
     ...: start = np.array([1,4,7])
     ...: end = np.array([5,6,9]) # different from sample for variety
     ...: 

# Mask of places to be edited
In [223]: mask = (start[:,None] <= r) & (end[:,None] >= r)

In [225]: print mask
[[False  True  True  True  True  True False False False False]
 [False False False False  True  True  True False False False]
 [False False False False False False False  True  True  True]]

In [226]: b[:len(mask)][mask] = a[:len(mask)][mask]

In [227]: a
Out[227]: 
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]])

In [228]: b
Out[228]: 
array([[ 0,  1,  2,  3,  4,  5,  0,  0,  0,  0],
       [ 0,  0,  0,  0, 14, 15, 16,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0, 27, 28, 29],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0]])
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks! This mask-trick looks neat! But does it cause any efficiency issues, i.e., making copy of the whole ndarray in this example? In the practical scenario, I have a pretty big ndarray, e.g, a.shape=(128, 32x32x32) and such value assignment would be repeated multiple times, depending on different start/end values at each iteration
@galactica Which copy are we referring to? Which step are you referring to?
@galactica If you are talking about the copy at : a[:len(mask)][mask], that can't be avoided with masking and you can't slice the whole thing in a vectorized manner. So, if memory efficiency is a concern, i guess keep the loop and use something like : b[0, start[0]:end[0]] = a[0, start[0]:end[0]] and so on.
sounds great! Thanks for the detailed explanations! Let's wait for a bit to see if there are other answers
What is 'r' in this example?
|

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.