8

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?

5 Answers 5

4

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))
Sign up to request clarification or add additional context in comments.

Comments

3

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]])

1 Comment

(a - 1) * -1 distributes to -a + 1 or 1 - a which is a bit clearer (also a decent bit faster)
3

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'.

2 Comments

Good solutions! Is the indexing logic you are using in the second one a case of fancy indexing, or does it go by another name?
The numpy documentation shows this as Advanced Indexing -> Integer Array Indexing. link. Both methods came from experience using Forth rather than Python/numpy.
2

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.

Comments

1

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

Comments

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.