73
from numpy import *
def swap_columns(my_array, col1, col2):
    temp = my_array[:,col1]
    my_array[:,col1] = my_array[:,col2]
    my_array[:,col2] = temp

Then

swap_columns(data, 0, 1)

Doesn't work. However, calling the code directly

temp = my_array[:,0]
my_array[:,0] = my_array[:,1]
my_array[:,1] = temp

Does. Why is this happening and how can I fix it? The Error says "IndexError: 0-d arrays can only use a single () or a list of newaxes (and a single ...) as an index", which implies the arguments aren't ints? I already tried converting the cols to int but that didn't solve it.

audacious ainsley
  • 731
  • 1
  • 5
  • 3

5 Answers5

129

There are two issues here. The first is that the data you pass to your function apparently isn't a two-dimensional NumPy array -- at least this is what the error message says.

The second issue is that the code does not do what you expect:

my_array = numpy.arange(9).reshape(3, 3)
# array([[0, 1, 2],
#        [3, 4, 5],
#        [6, 7, 8]])
temp = my_array[:, 0]
my_array[:, 0] = my_array[:, 1]
my_array[:, 1] = temp
# array([[1, 1, 2],
#        [4, 4, 5],
#        [7, 7, 8]])

The problem is that Numpy basic slicing does not create copies of the actual data, but rather a view to the same data. To make this work, you either have to copy explicitly

temp = numpy.copy(my_array[:, 0])
my_array[:, 0] = my_array[:, 1]
my_array[:, 1] = temp

or use advanced slicing

my_array[:, [0, 1]] = my_array[:, [1, 0]]
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Advanced slicing does not appear to copy the data either. – Mateen Ulhaq Jun 09 '17 at 11:03
  • 2
    @MateenUlhaq Be assured, it does. See the linked documentation, or try it yourself. – Sven Marnach Jun 09 '17 at 11:41
  • It certainly copies the data, but that means that the assignment assigns to a temporary object that is immediately discarded. – AI0867 Oct 06 '22 at 15:56
  • 2
    @AI0867 Only the advanced slicing on the right-hand side creates a copy. The advanced slicing in assignment-target position does not. You can try the code – it works just fine. (The reason for the different behaviour is that the `[]` operator in value context is handled by `__getitem__()`, while assignments with `[]` are handled by `__setitem__()`. It never makes sense to assign to a temporary copy, so the `__setitem__()` implementation for a NumPy array doesn't.) – Sven Marnach Oct 07 '22 at 11:26
  • I stand corrected. – AI0867 Oct 07 '22 at 12:55
45

I find the following the fastest:

my_array[:, 0], my_array[:, 1] = my_array[:, 1], my_array[:, 0].copy()

Time analysis of:

import numpy as np
my_array = np.arange(900).reshape(30, 30)

is as follows:

%timeit my_array[:, 0], my_array[:, 1] = my_array[:, 1], my_array[:, 0].copy()
The slowest run took 15.05 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 1.72 µs per loop

The advanced slicing times are:

%timeit my_array[:,[0, 1]] = my_array[:,[1, 0]]
The slowest run took 7.38 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 6.9 µs per loop
blaz
  • 4,108
  • 7
  • 29
  • 54
16

An elegant way to swap the columns in NumPy is analogous to swapping two variables in Python like so: x, y = y, x.

i, j = 0, 1
A.T[[i, j]] = A.T[[j, i]]  # swap the columns i and j

Suppose you have a numpy array A like this:

array([[ 0., -1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.],
       [ 0.,  0., -1.,  0.],
       [ 0.,  0.,  0., -1.]])

A.T[[0, 1]] = A.T[[1, 0]] will swap the first two columns:

array([[-1.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  1.],
       [ 0.,  0., -1.,  0.],
       [ 0.,  0.,  0., -1.]])
Jensun Ravichandran
  • 959
  • 1
  • 11
  • 28
15

Building up on @Sven's answer:

import numpy as np
my_array = np.arange(9).reshape(3, 3)
print my_array

[[0 1 2]
 [3 4 5]
 [6 7 8]]

def swap_cols(arr, frm, to):
    arr[:,[frm, to]] = arr[:,[to, frm]]

swap_cols(my_array, 0, 1)
print my_array

[[1 0 2]
 [4 3 5]
 [7 6 8]]

def swap_rows(arr, frm, to):
    arr[[frm, to],:] = arr[[to, frm],:]

my_array = np.arange(9).reshape(3, 3)
swap_rows(my_array, 0, 2)
print my_array

[[6 7 8]
 [3 4 5]
 [0 1 2]]
Renaud
  • 16,073
  • 6
  • 81
  • 79
1

If you want to simultaneously swap columns and assign to a new variable, the most clear and concise way I could figure out how to do it was this:

import numpy as np
test_arr = np.arange(12).reshape(4,3)
swapped = np.concatenate((test_arr[:, [1,0]], test_arr[:, 2:]), axis=1)

Note, if the further columns don't exist it will concatenate an on an empty array, meaning it will just swap the first two columns.

Andrew Holmgren
  • 1,225
  • 1
  • 11
  • 18