Classificando matrizes no NumPy por coluna

336

Como posso classificar uma matriz no NumPy pela enésima coluna?

Por exemplo,

a = array([[9, 2, 3],
           [4, 5, 6],
           [7, 0, 5]])

Gostaria de classificar as linhas pela segunda coluna, para que eu volte:

array([[7, 0, 5],
       [9, 2, 3],
       [4, 5, 6]])
Paul Wintz
fonte
8
Este é um péssimo exemplo, pois np.sort(a, axis=0)seria uma solução satisfatória para a matriz especificada. Sugeri uma edição com um exemplo melhor, mas fui rejeitada, embora, na verdade, a questão seja muito mais clara. O exemplo deve ser algo como a = numpy.array([[1, 2, 3], [6, 5, 2], [3, 1, 1]])com saída desejadaarray([[3, 1, 1], [1, 2, 3], [6, 5, 2]])
David
29
David, você não entende o assunto. Ele quer manter a ordem dentro de cada linha da mesma forma.
Marcorossi
@marcorossi Eu entendi, mas o exemplo foi muito mal formulado porque, como eu disse, havia várias respostas possíveis (as quais, no entanto, não teriam atendido à solicitação do OP). Uma edição posterior, com base no meu comentário, foi realmente aprovada (apesar de engraçado, o meu foi rejeitado). Então agora está tudo bem.
David

Respostas:

140

A resposta de @steve é realmente a maneira mais elegante de fazer isso.

Para a maneira "correta", consulte o argumento da palavra-chave da ordem numpy.ndarray.sort

No entanto, você precisará visualizar sua matriz como uma matriz com campos (uma matriz estruturada).

A maneira "correta" é muito feia se você não definiu inicialmente sua matriz com campos ...

Como um exemplo rápido, para classificá-lo e retornar uma cópia:

In [1]: import numpy as np

In [2]: a = np.array([[1,2,3],[4,5,6],[0,0,1]])

In [3]: np.sort(a.view('i8,i8,i8'), order=['f1'], axis=0).view(np.int)
Out[3]: 
array([[0, 0, 1],
       [1, 2, 3],
       [4, 5, 6]])

Para classificá-lo no local:

In [6]: a.view('i8,i8,i8').sort(order=['f1'], axis=0) #<-- returns None

In [7]: a
Out[7]: 
array([[0, 0, 1],
       [1, 2, 3],
       [4, 5, 6]])

@ Steve realmente é a maneira mais elegante de fazê-lo, tanto quanto eu sei ...

A única vantagem desse método é que o argumento "order" é uma lista dos campos para ordenar a pesquisa. Por exemplo, você pode classificar pela segunda coluna, depois pela terceira coluna e depois pela primeira coluna, fornecendo a ordem = ['f1', 'f2', 'f0'].

Joe Kington
fonte
3
Na minha 1.6.1rc1 numpy, levantaValueError: new type not compatible with array.
Clippit
9
Faria sentido registrar uma solicitação de recurso para que a maneira "correta" se tornasse menos feia?
Endolith 21/08/13
4
E se os valores na matriz forem float? Devo mudar alguma coisa?
Marco
11
E para o tipo híbrido, como a = np.array([['a',1,2,3],['b',4,5,6],['c',0,0,1]])qual abordagem devo seguir?
ePascoal
10
Uma grande vantagem desse método sobre o de Steve é ​​que ele permite que matrizes muito grandes sejam classificadas no local. Para um suficientemente grande variedade, os índices devolvidos pelo np.argsortthemselve pode levar até um monte de memória, e em cima disso, a indexação com uma matriz também irá gerar uma cópia da matriz que está sendo classificada.
ali_m
735

Suponho que isso funcione: a[a[:,1].argsort()]

Isso indica a segunda coluna de ae classifica-a com base nela de acordo.

Steve Tjoa
fonte
2
Isso não está claro, o que há 1aqui? o índice a ser classificado por?
precisa saber é
29
[:,1]indica a segunda coluna de a.
#
60
Se você quer o tipo reversa, modificar este sera[a[:,1].argsort()[::-1]]
Steven C. Howell
11
Parece simples e funciona! É mais rápido que np.sortou não?
Václav Pavlík
14
Acho isso mais fácil de ler:ind = np.argsort( a[:,1] ); a = a[ind]
poppie
32

Você pode classificar em várias colunas, de acordo com o método de Steve Tjoa, usando uma classificação estável como mergesort e classificando os índices das colunas menos significativas para as mais significativas:

a = a[a[:,2].argsort()] # First sort doesn't need to be stable.
a = a[a[:,1].argsort(kind='mergesort')]
a = a[a[:,0].argsort(kind='mergesort')]

Classifica pela coluna 0, depois 1 e 2.

JJ
fonte
4
Por que o First Sort não precisa ser estável?
Little Bobby Tables
10
Boa pergunta - estável significa que, quando há um empate, você mantém a ordem original e a ordem original do arquivo não classificado é irrelevante.
JJ
Este parece ser um ponto realmente super importante. ter uma lista que silenciosamente não classifica seria ruim.
Clumsy cat
20

A partir da documentação wiki Python , eu acho que você pode fazer:

a = ([[1, 2, 3], [4, 5, 6], [0, 0, 1]]); 
a = sorted(a, key=lambda a_entry: a_entry[1]) 
print a

A saída é:

[[[0, 0, 1], [1, 2, 3], [4, 5, 6]]]
user541064
fonte
20
Com esta solução, obtém-se uma lista em vez de uma matriz NumPy, portanto, isso nem sempre é conveniente (consome mais memória, provavelmente é mais lento, etc.).
Eric O Lebigot
18

Caso alguém queira fazer uso da classificação em uma parte crítica de seus programas, aqui está uma comparação de desempenho para as diferentes propostas:

import numpy as np
table = np.random.rand(5000, 10)

%timeit table.view('f8,f8,f8,f8,f8,f8,f8,f8,f8,f8').sort(order=['f9'], axis=0)
1000 loops, best of 3: 1.88 ms per loop

%timeit table[table[:,9].argsort()]
10000 loops, best of 3: 180 µs per loop

import pandas as pd
df = pd.DataFrame(table)
%timeit df.sort_values(9, ascending=True)
1000 loops, best of 3: 400 µs per loop

Portanto, parece que a indexação com argsort é o método mais rápido até agora ...

prl900
fonte
16

Na lista de discussão do NumPy , aqui está outra solução:

>>> a
array([[1, 2],
       [0, 0],
       [1, 0],
       [0, 2],
       [2, 1],
       [1, 0],
       [1, 0],
       [0, 0],
       [1, 0],
      [2, 2]])
>>> a[np.lexsort(np.fliplr(a).T)]
array([[0, 0],
       [0, 0],
       [0, 2],
       [1, 0],
       [1, 0],
       [1, 0],
       [1, 0],
       [1, 2],
       [2, 1],
       [2, 2]])
fgregg
fonte
3
A generalização correta é a[np.lexsort(a.T[cols])]. onde cols=[1]na pergunta original.
Controlado por rádio
5

Eu tive um problema parecido.

Meu problema:

Quero calcular um SVD e preciso classificar meus valores próprios em ordem decrescente. Mas quero manter o mapeamento entre valores próprios e vetores próprios. Meus autovalores estavam na primeira linha e o vetor próprio correspondente abaixo dele na mesma coluna.

Então, eu quero classificar uma matriz bidimensional em colunas pela primeira linha em ordem decrescente.

Minha solução

a = a[::, a[0,].argsort()[::-1]]

Então, como isso funciona?

a[0,] é apenas a primeira linha pela qual quero classificar.

Agora eu uso o argsort para obter a ordem dos índices.

Eu uso [::-1]porque preciso de ordem decrescente.

Por fim, eu uso a[::, ...]para obter uma exibição com as colunas na ordem correta.

xuma202
fonte
1

Um lexsortexemplo um pouco mais complicado - descendo na 1ª coluna, subindo secundariamente na 2ª. Os truques lexsortsão que ele classifica em linhas (daí o .T), e dá prioridade ao último.

In [120]: b=np.array([[1,2,1],[3,1,2],[1,1,3],[2,3,4],[3,2,5],[2,1,6]])
In [121]: b
Out[121]: 
array([[1, 2, 1],
       [3, 1, 2],
       [1, 1, 3],
       [2, 3, 4],
       [3, 2, 5],
       [2, 1, 6]])
In [122]: b[np.lexsort(([1,-1]*b[:,[1,0]]).T)]
Out[122]: 
array([[3, 1, 2],
       [3, 2, 5],
       [2, 1, 6],
       [2, 3, 4],
       [1, 1, 3],
       [1, 2, 1]])
hpaulj
fonte
0

Aqui está outra solução considerando todas as colunas (maneira mais compacta da resposta de JJ );

ar=np.array([[0, 0, 0, 1],
             [1, 0, 1, 0],
             [0, 1, 0, 0],
             [1, 0, 0, 1],
             [0, 0, 1, 0],
             [1, 1, 0, 0]])

Classifique com lexsort,

ar[np.lexsort(([ar[:, i] for i in range(ar.shape[1]-1, -1, -1)]))]

Resultado:

array([[0, 0, 0, 1],
       [0, 0, 1, 0],
       [0, 1, 0, 0],
       [1, 0, 0, 1],
       [1, 0, 1, 0],
       [1, 1, 0, 0]])
Sefa
fonte
0

Simplesmente usando a classificação, use o número da coluna com base no qual você deseja classificar.

a = np.array([1,1], [1,-1], [-1,1], [-1,-1]])
print (a)
a=a.tolist() 
a = np.array(sorted(a, key=lambda a_entry: a_entry[0]))
print (a)
Jerin
fonte
0

É uma pergunta antiga, mas se você precisar generalizar isso para matrizes de mais de duas dimensões, aqui está a solução que pode ser facilmente generalizada:

np.einsum('ij->ij', a[a[:,1].argsort(),:])

Isso é um exagero para duas dimensões e a[a[:,1].argsort()]seria suficiente por resposta de @ steve, no entanto, essa resposta não pode ser generalizada para dimensões superiores. Você pode encontrar um exemplo de matriz 3D nesta pergunta.

Resultado:

[[7 0 5]
 [9 2 3]
 [4 5 6]]
Ehsan
fonte