2

I need to sort matrices according to the descending order of the values in another matrix.

E.g. in a first step I would have the following matrix A:

1 0 1 0 1
0 1 0 1 0
0 1 0 1 1
1 0 1 0 0

Then for the procedure I am following I need to take the rows of the matrix as binary numbers and sort them in descending order of their binary value.

I am doing this the following way:

for i in range(0,num_rows):   
    for j in range(0,num_cols):
        row_val[i] = row_val[i] + A[i][j] * (2 ** (num_cols - 1 - j))

This gets me a 4x1 vector row_val with the following values:

21
10
11
20

Now I am sorting the rows of the matrix according to row_val by

A = [x for _,x in sorted(zip(row_val,A),reverse=True)]

This works perfectly fine I get the matrix A:

1 0 1 0 1
1 0 1 0 0
0 1 0 1 1
0 1 0 1 0

However now I need to apply the same procedure to the columns. So I calculate a the col_val vector with the binary values of the columns:

12
3
12
3
3

To sort the matrix A according to the vector col_val I thought I could just transpose matrix A and then do the same as before:

At = np.transpose(A)
At = [y for _,y in sorted(zip(col_val,At),reverse=True)]

Unfortunatly this fails with the error message

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

I am suspecting that this might be because there are several entries with the same value in vector col_val, however in an example shown in another question the sorting seems to work for a case with several equal entries.

glibdud
  • 7,550
  • 4
  • 27
  • 37
Axel
  • 1,415
  • 1
  • 16
  • 40

1 Answers1

2

Your suspicion is correct, you can't sort multidimensional numpy arrays using the Python builtin sorted because the comparison of two rows, say, will yield a row of truth values instead of a single one

A[0] < A[1]
# array([False,  True, False,  True, False])

so sorted can't tell which should go before the other.

In your first example this is masked by lexicographic ordering of tuples: Because tuples are compared left to right and because row_val has unique entries the comparison never looks at the second elements.

But in your second example because some col_val entries are equal, the comparison will look at At for a tie breaker which is where the exception occurs.

Here is a working method which uses numpy methods:

A[np.argsort(np.packbits(A, axis=1).ravel())[::-1]]
# array([[1, 0, 1, 0, 1],
#        [1, 0, 1, 0, 0],
#        [0, 1, 0, 1, 1],
#        [0, 1, 0, 1, 0]])
A[:, np.argsort(np.packbits(A, axis=0).ravel())[::-1]]
# array([[1, 1, 1, 0, 0],
#        [0, 0, 0, 1, 1],
#        [1, 0, 0, 1, 1],
#        [0, 1, 1, 0, 0]])

Explanation:

np.packbits as the name suggests packs binary vectors into bit field; it is almost equivalent to your hand-written code - there is one small difference in that packbits operates on chunks of 8 and pads with zero on the right, so for example [1, 1] will go to 192, not 3.

np.argsort does an indirect sort, so it doesn't actually move the elements of its operand A but just writes down the sequence of indices I into A which would sort it A[I] == np.sort(A). This is useful when we want to sort something based on the order of something else like in this case.

Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
  • Wow that is impressively short! Can you explain a little bit how this works? – Axel Apr 06 '18 at 10:50
  • Thanks that helps. So although the calculated "binary values" of the columns and rows are not the same as in my example that shouldn't have any influence on the resulting new order of the matrix, right? For the row sorting I could also write `A[np.argsort(np.packbits(A, axis=1).ravel())[::-1],:]` to make clear that only the rows are taken with every column of them? – Axel Apr 06 '18 at 11:10
  • @Axel yes and yes. – Paul Panzer Apr 06 '18 at 11:13
  • Thank you very much for your help! – Axel Apr 06 '18 at 11:14