Reputation: 5914
I'd like to swap elements of the i-th row a Nx2 numpy array my_array
if a condition swap[i]
is True
.
My attempt:
def swap_positions_conditionally(my_array, swap):
for i in range(np.shape(my_array)[0]):
if swap[i]:
my_array[i] = my_array[i][::-1]
return my_array
works fine, e.g. given
my_array = array([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
swap = array([0, 0, 1, 1, 0])
yields the expected result
[[0 1]
[2 3]
[5 4]
[7 6]
[8 9]]
However, there's likely a more idiomatic expression to rewrite my swap_position_conditionally
.
What would be a better (and more efficient) way to write it?
Upvotes: 0
Views: 669
Reputation: 19123
And here's one using Numpy's boolean indexing directly:
import numpy as np
my_array = np.asarray([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
swap = np.array([0, 0, 1, 1, 0], dtype=bool)
my_array[swap, :] = my_array[swap,:][:,(1,0)]
Breaking down the key line:
my_array[swap, :] =
means "Assign to the rows where swap
is true"my_array[swap,:]
means "select the whole row where swap
is true"[:,(1,0)]
means "for each row of what's to the left, swap the columns 0 and 1"Common setup for all tests (seed ensures sequences are identical):
import timeit
setup= '''
import numpy as np
np.random.seed(42)
my_array = np.random.random([10000,2])
swap = np.random.random([10000]) > 0.5
'''
All tests run for 1000 iterations
timeit.timeit('swap_positions_conditionally(my_array, swap)', setup=setup, number=1000)
Added the definition of swap_positions_conditionally
to setup
as shown in the question.
timeit.timeit('my_array[swap, :] = my_array[swap,:][:,(1,0)]', setup=setup, number=1000)
timeit.timeit('np.where(swap[:,None]!=1,my_array,my_array[:,::-1])', setup=setup, number=1000)
timeit.timeit('np.take_along_axis(my_array, np.c_[swap, 1-swap], axis=1)', setup=setup, number=1000)
timeit.timeit('my_array[swap,:] = my_array[swap,::-1]', setup=setup, number=1000)
Profiling shows Divakar's version is the fastest. Whichever is more intuitive or readable is a matter of flavors, you can pick the one you prefer (I personally am a fan of the indexing notation readability-wise though...)
Upvotes: 1
Reputation: 88236
Here's one using np.take_along_axis
:
np.take_along_axis(my_array, np.c_[swap, 1-swap], axis=1)
array([[0, 1],
[2, 3],
[5, 4],
[7, 6],
[8, 9]])
Or a boolean indexing based one:
swap = swap.astype(bool)
my_array[swap,:] = my_array[swap,::-1]
Upvotes: 1
Reputation: 221574
Here's one way that swaps for Nx2
array and flips for more number of columns with the negative stepsize slicing as you were attempting -
In [56]: np.where(swap[:,None]==1, my_array[:,::-1], my_array)
Out[56]:
array([[0, 1],
[2, 3],
[5, 4],
[7, 6],
[8, 9]])
Syntax is : np.where(conditional_statement, choose_for_True, choose_for_False)
. So, in our case, we want to flip/swap when swap
is 1
, else don't. That [:,None]
part is needed to do this elementwise across each row. If swap
is already a boolean array, skip the comparison part.
Upvotes: 1