4
\$\begingroup\$

Here is the problem:

Given a numpy array 'a' that contains n elements, denote by b the set of its unique values ​​in ascending order, denote by m the size of array b. You need to create a numpy array with dimensions n×m , in each row of which there must be a value of 1 in case it is equal to the value of the given index of array b, in other places it must be 0.

import numpy as np


def convert(a):
    b = np.unique(sorted(a))
    result = []
    for i in a:
        result.append((b == i) * 1)
    return np.array(result)


a = np.array([1, 1, 2, 3, 2, 4, 5, 2, 3, 4, 5, 1, 1])
b = np.unique(sorted(a))
print(convert(a))

This is my solution. is there some improvments that I can make? I'm not sure about declaring regular list to the result and then converting it into np.array.

\$\endgroup\$
2
  • \$\begingroup\$ Please do not edit the question with suggestions from answers. If you want a new round of feedback with updated code, you can post a new question. \$\endgroup\$
    – Reinderien
    Apr 7, 2021 at 18:29
  • 1
    \$\begingroup\$ I've mistaken, when copied the code from my editor, that line was unnecessary. \$\endgroup\$ Apr 7, 2021 at 18:41

2 Answers 2

4
\$\begingroup\$

Remove sorted

From the docs: numpy.unique returns the sorted unique elements of an array.

You can simply remove the call to sorted:

b = np.unique(sorted(a))

# produces the same result as

b = np.unique(a)

List comprehension

In most cases you can and should avoid this pattern of list creation:

result = []
for i in a:
    result.append((b == i) * 1)

It can be replaced by a concise list comprehension and directly passed to np.array:

result = np.array([(b == i) * 1 for i in a])

# or directly return it (if applicable)
return np.array([(b == i) * 1 for i in a])

List comprehensions are more pythonic and often faster. Generally, not mutating the list object is also less error-prone.


There might be a better way to map lambda x: (uniques == x) * 1 over the input array a. Here's a discussion on the topic on StackOverflow: Most efficient way to map function over numpy array. Seems like using np.vectorize should be avoided for performance reasons.

Using map might be similiar to the list comprehension performance-wise (I did not properly test performance here):

def convert_listcomp(a):
    uniques = np.unique(a)
    return np.array([(b == i) * 1 for i in a])

def convert_map(a):
    uniques = np.unique(a)
    return np.array(list(map(lambda x: (uniques == x) * 1, a)))
\$\endgroup\$
1
\$\begingroup\$

There's always the one-liner that assumes a is a row vector (1d array):

(a.reshape((-1, 1)) == np.unique(a)).astype(int)

This works by broadcasting the == operation. Let us see how.

When you ask numpy to apply an operation, first it checks if the dimensions are compatible. If they aren't, an exception is raised. For example, try typing np.ones(3) - np.ones(4) in an interpreter. After pressing enter, you should see the message

ValueError: operands could not be broadcast together with shapes (3,) (4,).

This happens, because 3 != 4. Duh. But there's more.

Try a.reshape((-1, 1)) == np.unique(a). Despite n!=m usually holding, numpy happily calculates a matrix of shape (n, m). Why?

This is the magic of broadcasting:

When operating on two arrays, NumPy compares their shapes element-wise. It starts with the trailing (i.e. rightmost) dimensions and works its way left. Two dimensions are compatible when

  1. they are equal, or

  2. one of them is 1

If these conditions are not met, a ValueError: operands could not be broadcast together exception is thrown, indicating that the arrays have incompatible shapes. The size of the resulting array is the size that is not 1 along each axis of the inputs.

How is this rule applied here? Well, the shape of x = a.reshape((-1, 1)) is (n, 1), the shape of y = np.unique(a) is (1, m), so the second point from above holds. Therefore numpy expands x from shape (n, 1) to xx of shape (n, m) by "copying" (to the best of my knowledge there's no copying occurring) its values along the second axis, i.e. respecting the rule

xx[j, k] = x[j] for all j=1..n, k=1..m.

Similarly, y is expanded from shape (1, m) to yy of shape (n, m) respecting

yy[j, k] = y[k] for all j=1..n, k=1..m

and the operation is applied to xx and yy as usual, i.e.

x == y   ~>   xx == yy   ~>    :)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.