Como obter índices de uma matriz classificada em Python

200

Eu tenho uma lista numérica:

myList = [1, 2, 3, 100, 5]

Agora, se eu classificar esta lista para obter [1, 2, 3, 5, 100]. O que eu quero é os índices dos elementos da lista original na ordem classificada, ou seja, [0, 1, 2, 4, 3] --- a função de classificação ala MATLAB que retorna valores e índices.

Gyan
fonte
2
Veja também: stackoverflow.com/questions/7851077/…
kevinarpe 25/14
@unutbu Este não é um idiota (IMO). A questão não contradiz usando Numpy.argsort ()
Amit
@amit: O que você quer dizer com "não contradiz"?
Unutbu
@unutbu Numpy.argsort () é uma boa resposta para essa pergunta, pode ser um engodo para o outro segmento vinculado (que você também fechou e acho que você não deveria ter), mas não para o que você mencionou, como Numpy. argsort () é uma boa resposta para esses dois, mas NÃO para o que você se referiu.
005156
1
Infelizmente, essa pergunta tem uma falha grave em sua escolha de exemplo, pois duas maneiras diferentes de ler a pergunta dariam a mesma resposta quando a entrada é apenas uma transposição fora da ordem classificada.

Respostas:

147

Algo como o próximo:

>>> myList = [1, 2, 3, 100, 5]
>>> [i[0] for i in sorted(enumerate(myList), key=lambda x:x[1])]
[0, 1, 2, 4, 3]

enumerate(myList) fornece uma lista contendo tuplas de (índice, valor):

[(0, 1), (1, 2), (2, 3), (3, 100), (4, 5)]

Você classifica a lista passando-a sortede especificando uma função para extrair a chave de classificação (o segundo elemento de cada tupla; lambdaé para isso que serve. Finalmente, o índice original de cada elemento classificado é extraído usando a [i[0] for i in ...]compreensão da lista.

Roman Bodnarchuk
fonte
7
você pode usar itemgetter(1)em vez da função lambda
John La Rooy
4
@gnibbler está se referindo à itemgetterfunção no operatormódulo, FYI. Então faça from operator import itemgetterpara usá-lo.
Lauritz V. Thaulow,
1
você pode obter a lista e indicies classificadas usando zip:sorted_items, sorted_inds = zip(*sorted([(i,e) for i,e in enumerate(my_list)], key=itemgetter(1)))
Charles L.
@RomanBodnarchuk isso não funciona, x = [3,1,2]; numpy.argsort(x)gera [1,2,0].
Shahar_m 14/05/19
24

As respostas enumeratesão boas, mas eu pessoalmente não gosto do lambda usado para classificar pelo valor. O seguinte apenas reverte o índice e o valor e classifica isso. Portanto, ele primeiro classifica por valor, depois por índice.

sorted((e,i) for i,e in enumerate(myList))
Ant6n
fonte
11

Resposta atualizada com enumeratee itemgetter:

sorted(enumerate(a), key=lambda x: x[1])
# [(0, 1), (1, 2), (2, 3), (4, 5), (3, 100)]

Compacte as listas: O primeiro elemento da tupla será o índice, o segundo é o valor (em seguida, classifique-o usando o segundo valor da tupla x[1], x é a tupla)

Ou usando itemgetterdo operatormódulo`:

from operator import itemgetter
sorted(enumerate(a), key=itemgetter(1))
Matt
fonte
1
enumerar parece mais apropriado que zip neste caso
njzk2 21/05
10

Fiz uma rápida verificação de desempenho nelas com o perfplot (um projeto meu) e constatei que é difícil recomendar qualquer outra coisa além de entorpecente (observe a escala de log):

insira a descrição da imagem aqui


Código para reproduzir o gráfico:

import perfplot
import numpy


def sorted_enumerate(seq):
    return [i for (v, i) in sorted((v, i) for (i, v) in enumerate(seq))]


def sorted_enumerate_key(seq):
    return [x for x, y in sorted(enumerate(seq), key=lambda x: x[1])]


def sorted_range(seq):
    return sorted(range(len(seq)), key=seq.__getitem__)


def numpy_argsort(x):
    return numpy.argsort(x)


perfplot.save(
    "argsort.png",
    setup=lambda n: numpy.random.rand(n),
    kernels=[sorted_enumerate, sorted_enumerate_key, sorted_range, numpy_argsort],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(x)",
)
Nico Schlömer
fonte
6

Se você não quiser usar numpy,

sorted(range(len(seq)), key=seq.__getitem__)

é mais rápido, como demonstrado aqui .

mab
fonte
5

Essencialmente, você precisa fazer um argsort, de qual implementação você depende depende se você deseja usar bibliotecas externas (por exemplo, NumPy) ou se deseja permanecer em Python puro sem dependências.

A pergunta que você precisa se perguntar é: você quer o

  • índices que ordenariam a matriz / lista
  • índices que os elementos teriam na lista / matriz classificada

Infelizmente, o exemplo da pergunta não deixa claro o que é desejado, porque ambos darão o mesmo resultado:

>>> arr = np.array([1, 2, 3, 100, 5])

>>> np.argsort(np.argsort(arr))
array([0, 1, 2, 4, 3], dtype=int64)

>>> np.argsort(arr)
array([0, 1, 2, 4, 3], dtype=int64)

Escolhendo a argsortimplementação

Se você tiver o NumPy à sua disposição, poderá simplesmente usar a função numpy.argsortou método numpy.ndarray.argsort.

Uma implementação sem NumPy já foi mencionada em algumas outras respostas, portanto, apenas recapitularei a solução mais rápida de acordo com a resposta de referência aqui

def argsort(l):
    return sorted(range(len(l)), key=l.__getitem__)

Obtendo os índices que ordenariam a matriz / lista

Para obter os índices que ordenariam a matriz / lista, você pode simplesmente chamar argsorta matriz ou a lista. Estou usando as versões do NumPy aqui, mas a implementação do Python deve fornecer os mesmos resultados

>>> arr = np.array([3, 1, 2, 4])
>>> np.argsort(arr)
array([1, 2, 0, 3], dtype=int64)

O resultado contém os índices necessários para obter a matriz classificada.

Como a matriz classificada seria, [1, 2, 3, 4]a matriz argsorted contém os índices desses elementos no original.

  • O menor valor é 1e está no índice 1do original, portanto, o primeiro elemento do resultado é 1.
  • O 2está no índice 2no original, portanto, o segundo elemento do resultado é 2.
  • O 3está no índice 0no original, portanto, o terceiro elemento do resultado está 0.
  • O maior valor 4e está no índice 3no original, portanto, o último elemento do resultado é 3.

Obtendo os índices que os elementos teriam na matriz / lista classificada

Nesse caso, você precisaria aplicar argsort duas vezes :

>>> arr = np.array([3, 1, 2, 4])
>>> np.argsort(np.argsort(arr))
array([2, 0, 1, 3], dtype=int64)

Nesse caso :

  • o primeiro elemento do original é 3, que é o terceiro maior valor, portanto, ele teria um índice 2na matriz / lista classificada, portanto, o primeiro elemento 2.
  • o segundo elemento do original é 1, que é o menor valor, portanto, ele teria um índice 0na matriz / lista classificada, de modo que o segundo elemento é 0.
  • o terceiro elemento do original é 2, que é o segundo menor valor; portanto, ele teria um índice 1na matriz / lista classificada, de modo que o terceiro elemento é 1.
  • o quarto elemento do original é 4qual é o maior valor, portanto, ele teria um índice 3na matriz / lista classificada, e o último elemento 3.
MSeifert
fonte
4

As outras respostas estão erradas.

Correr argsortuma vez não é a solução. Por exemplo, o seguinte código:

import numpy as np
x = [3,1,2]
np.argsort(x)

produz array([1, 2, 0], dtype=int64)que não é o que queremos.

A resposta deve ser executar argsortduas vezes:

import numpy as np
x = [3,1,2]
np.argsort(np.argsort(x))

array([2, 0, 1], dtype=int64)como esperado.

shahar_m
fonte
Sua reivindicação torna x[2](3) o menor elemento e x[1](1) o maior elemento (já que a classificação de números inteiros os ordena do menor para o maior valor). Além disso, com o exemplo dos OPs, um único np.argsort([1, 2, 3, 100, 5])rendimento array([0, 1, 2, 4, 3]), que parece ser o índice que o OP deseja.
0 0
1
@ 0 0 seu exemplo é um caso específico. Se corrermos arr = [1,2,3,100, 5, 9] res = np.argsort(arr) print(res), entendemos o [0 1 2 4 5 3]que está errado.
shahar_m 19/01
Não estou claro o que está errado: arr[res]rendimentos array([ 1, 2, 3, 5, 9, 100]), o que parece estar perfeitamente bem, pois a matriz resultante está em ordem (crescente).
0 0
@ 0 0 arr=[1,2,3,100, 5, 9], espero que a saída seja inds=[0,1,2,5,3,4], porque esta é a ordem em que você ordenará os elementos (cada vez mais) - 1 está no lugar 0s, 2 no 1º lugar, ...., 5 no 3º lugar e 9 no 4º lugar. Para obter essa saída ( inds), preciso executar argsortduas vezes, como mencionei.
shahar_m 20/01
Portanto, esses índices são uma espécie de classificação dos elementos da matriz (0º lugar, 1º lugar, etc.). Dada a menção do OP ao MATLABsort , acho que o OP deseja a outra funcionalidade, da mesma forma que np.argsortnormalmente é usada (onde é possível usar arr[np.argsort[arr]]para obter a matriz classificada, como no último exemplo do MATLAB). Sua resposta se aplica a este caso / pergunta .
0 0
0

Importar numpy como np

PARA ÍNDICE

S=[11,2,44,55,66,0,10,3,33]

r=np.argsort(S)

[output]=array([5, 1, 7, 6, 0, 8, 2, 3, 4])

argsort Retorna os índices de S na ordem classificada

PARA VALOR

np.sort(S)

[output]=array([ 0,  2,  3, 10, 11, 33, 44, 55, 66])
negi
fonte
0

Criaremos outra matriz de índices de 0 a n-1. Em seguida, compacte-a na matriz original e classifique-a com base nos valores originais

ar = [1,2,3,4,5]
new_ar = list(zip(ar,[i for i in range(len(ar))]))
new_ar.sort()

`

Jai dewani
fonte