Legatro
Legatro

Reputation: 3732

numpy swap multiple elements in an array

I have a numpy array containing a random spread of 1s and 0s. I want to replace all the 1s with a 0 and all the zeros with a 1.

arr[arr == 0] = 2
arr[arr == 1] = 0
arr[arr == 2] = 1

I'm currently having to use a temporary value (2 in this case) in order to avoid all the 0s becoming 1s and then subsequently making the entire array full of 0s. Is there a more elegant/efficient way to do this?

Upvotes: 8

Views: 2661

Answers (5)

max9111
max9111

Reputation: 6482

If an efficient solution is more important than an elegant one, you could write a quite simple Numba solution.

Example

import numba as nb
import numpy as np

@nb.njit()
def nb_where(arr):
  for i in range(arr.shape[0]):
    for j in range(arr.shape[1]):
      if arr[i,j]==1:
        arr[i,j] = 0
      else:
        arr[i,j] = 1
  return arr

Timings

a = np.eye(1000, dtype=int)
np.where(a == 0, 1, 0) #timgeb    -> 2.06ms 
a^1                    #Tls Chris -> 1.31ms 
nb_where(a)                       -> 152 µs

Upvotes: 1

timgeb
timgeb

Reputation: 78690

Given

>>> a
array([[1, 0, 0, 1],
       [1, 1, 1, 0]])

you can use numpy.where

>>> np.where(a == 0, 1, 0) # read as (if, then, else)
array([[0, 1, 1, 0],
       [0, 0, 0, 1]])

... or alternatively negate a and do some typecasting.

>>> (~a.astype(bool)).astype(int)
array([[0, 1, 1, 0],
       [0, 0, 0, 1]])

(IPython) timings: not much difference.

>>> a = np.eye(1000, dtype=int)
>>> %timeit np.where(a == 0, 1, 0)
1.56 ms ± 2.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit (~a.astype(bool)).astype(int)
1.74 ms ± 87.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Timings for other people's answers:

>>> %timeit a^1 # Tls Chris
920 µs ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit np.array([1, 0])[a] # Tls Chris
1.4 ms ± 102 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit (a - 1)*-1 # sacul
1.57 ms ± 13.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit 1 - a # user3483203
905 µs ± 2.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

My opinion: a^1 and 1 - a are clean, elegant and fast. Using np.where works with any values you might want to swap.

Upvotes: 2

Tls Chris
Tls Chris

Reputation: 3824

For your specific values bitwise xor with 1.

In [19]: a=np.random.randint(2, size=10)

In [18]: a
Out[18]: array([1, 1, 1, 1, 1, 1, 0, 0, 1, 1])

In [19]: a^1
Out[19]: array([0, 0, 0, 0, 0, 0, 1, 1, 0, 0])

A more general solution for int types.

In [62]: convert=np.array([1,0])

In [63]: convert[a]
Out[63]: array([0, 0, 0, 0, 0, 0, 1, 1, 0, 0])

Changing the contents of the 'convert' array means a range of values can be mapped. The result uses the contents of array 'a' as the index into the array 'convert'.

Upvotes: 3

sacuL
sacuL

Reputation: 51335

Here's a solution that's very specific to your problem, but should also be very fast. Given the array:

>>> a
array([[1, 0, 0, 1],
       [1, 1, 1, 0]])

You can subtract 1 from all the values and multiply by negative 1:

>>> (a-1)*-1
array([[0, 1, 1, 0],
       [0, 0, 0, 1]])

Upvotes: 3

jpp
jpp

Reputation: 164683

You can calculate and store your Boolean indexers before overwriting any values:

ones = a == 1
zeros = a == 0

a[ones] = 0
a[zeros] = 1

The solution also works if you have values other than 0 and 1.


If you don't need an in place solution, you can use np.where:

a = np.where(ones, 0, np.where(zeros, 1, a))

Upvotes: 4

Related Questions