Trovare l'indice degli elementi in base a una condizione utilizzando la comprensione delle liste di Python


119

Il seguente codice Python sembra essere molto prolisso quando proviene da uno sfondo Matlab

>>> a = [1, 2, 3, 1, 2, 3]
>>> [index for index,value in enumerate(a) if value > 2]
[2, 5]

Quando sono in Matlab posso scrivere:

>> a = [1, 2, 3, 1, 2, 3];
>> find(a>2)
ans =
     3     6

Esiste un metodo a mano breve per scrivere questo in Python o rimango solo con la versione lunga?


Grazie per tutti i suggerimenti e la spiegazione del fondamento logico della sintassi di Python.

Dopo aver trovato quanto segue sul sito web di numpy, penso di aver trovato una soluzione che mi piace:

http://docs.scipy.org/doc/numpy/user/basics.indexing.html#boolean-or-mask-index-arrays

Applicare le informazioni da quel sito web al mio problema sopra, darebbe quanto segue:

>>> from numpy import array
>>> a = array([1, 2, 3, 1, 2, 3])
>>> b = a>2 
array([False, False, True, False, False, True], dtype=bool)
>>> r = array(range(len(b)))
>>> r(b)
[2, 5]

Il seguente dovrebbe quindi funzionare (ma non ho un interprete Python a portata di mano per testarlo):

class my_array(numpy.array):
    def find(self, b):
        r = array(range(len(b)))
        return r(b)


>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]

6
Che ne dici [idx for idx in range(len(a)) if a[idx] > 2]? Il motivo per cui questo è un po 'scomodo da fare in Python è perché non usa gli indici tanto quanto gli altri linguaggi.
NullUserException

Risposte:


77
  • In Python, non useresti affatto gli indici per questo, ma ti occuperai solo dei valori— [value for value in a if value > 2]. Di solito trattare con gli indici significa che non stai facendo qualcosa nel modo migliore.

  • Se non bisogno di un'API simile a Matlab di, si usa NumPy , un pacchetto per gli array multidimensionali e la matematica numerica in Python, che è fortemente ispirato Matlab. Useresti un array numpy invece di un elenco.

    >>> import numpy
    >>> a = numpy.array([1, 2, 3, 1, 2, 3])
    >>> a
    array([1, 2, 3, 1, 2, 3])
    >>> numpy.where(a > 2)
    (array([2, 5]),)
    >>> a > 2
    array([False, False,  True, False, False,  True], dtype=bool)
    >>> a[numpy.where(a > 2)]
    array([3, 3])
    >>> a[a > 2]
    array([3, 3])

2
hai elenchi, uno per gli intervalli e uno per gli angoli, vuoi filtrare i valori dell'intervallo che sono al di sopra di una certa soglia. Come filtrate nel "modo migliore" anche gli angoli corrispondenti a tali intervalli?
Mehdi

3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham

7
"In Python, non useresti affatto gli indici per questo, ma gestisci solo i valori" questa affermazione mostra che non hai fatto abbastanza analisi dei dati e modellazione di machine learning. Gli indici di un tensore basati su determinate condizioni vengono utilizzati per filtrare un altro tensore.
horaceT

63

Un altro modo:

>>> [i for i in range(len(a)) if a[i] > 2]
[2, 5]

In generale, ricorda che sebbene findsia una funzione già pronta, le comprensioni delle liste sono una soluzione generale e quindi molto potente . Niente ti impedisce di scrivere una findfunzione in Python e di usarla successivamente come desideri. Vale a dire:

>>> def find_indices(lst, condition):
...   return [i for i, elem in enumerate(lst) if condition(elem)]
... 
>>> find_indices(a, lambda e: e > 2)
[2, 5]

Nota che sto usando gli elenchi qui per imitare Matlab. Sarebbe più Pythonic usare generatori e iteratori.


2
L'OP potrebbe averlo scritto come [i for i,v in enumerate(a) if v > 2]invece.
NullUserException

Non è più breve, è più lungo. Sostituisci indexcon ie valuecon vnell'originale e conta i caratteri.
agf

@NullUser, agf: hai ragione, ma il punto principale è la seconda parte :)
Eli Bendersky

1
L'utilizzo di enumerateover range(len(...))è sia più robusto che più efficiente.
Mike Graham

1
@ Mike Graham: Sono d'accordo - cambierà la find_indicesfunzione da usareenumerate
Eli Bendersky

22

Per me funziona bene:

>>> import numpy as np
>>> a = np.array([1, 2, 3, 1, 2, 3])
>>> np.where(a > 2)[0]
[2 5]

6

Forse un'altra domanda è: "cosa farai con quegli indici una volta ottenuti?" Se hai intenzione di usarli per creare un altro elenco, in Python sono un passaggio intermedio non necessario. Se vuoi che tutti i valori corrispondano a una data condizione, usa semplicemente il filtro integrato:

matchingVals = filter(lambda x : x>2, a)

Oppure scrivi la tua lista comprhension:

matchingVals = [x for x in a if x > 2]

Se si desidera rimuoverli dall'elenco, il metodo pitonico non è quello di rimuovere necessariamente dall'elenco, ma scrivere una comprensione dell'elenco come se si stesse creando un nuovo elenco e assegnando nuovamente sul posto usando il listvar[:]sulla mano sinistra -lato:

a[:] = [x for x in a if x <= 2]

Matlab fornisce findperché il suo modello incentrato sull'array funziona selezionando gli elementi utilizzando i loro indici di array. È possibile fare questo in Python, certamente, ma il modo più Pythonic sta usando iteratori e generatori, come già detto da @EliBendersky.


Paul, non ho ancora riscontrato la necessità di questo in uno script / funzione / classe. È più per il test interattivo di una classe che sto scrivendo.
Lee

@ Mike - grazie per la modifica, ma volevo davvero a[:] = ...- vedi la risposta di Alex Martelli a questa domanda stackoverflow.com/questions/1352885/… .
PaulMcG

@ Paul, pensavo (e speravo!) Che non intendessi davvero dalla tua descrizione che avresti "creato una nuova lista"; Trovo che i programmi tendano ad essere più pronti a capire e mantenere quando mutano i dati esistenti con molta parsimonia. In ogni caso, mi dispiace oltrepassare il limite: dovresti sicuramente essere in grado di modificare il tuo post come desideri.
Mike Graham

6

Anche se è una risposta tardiva: penso che questa sia ancora un'ottima domanda e IMHO Python (senza librerie o toolkit aggiuntivi come numpy) manca ancora di un metodo conveniente per accedere agli indici degli elementi della lista secondo un filtro definito manualmente.

È possibile definire manualmente una funzione, che fornisce tale funzionalità:

def indices(list, filtr=lambda x: bool(x)):
    return [i for i,x in enumerate(list) if filtr(x)]

print(indices([1,0,3,5,1], lambda x: x==1))

Rendimenti: [0, 4]

Nella mia immaginazione il modo perfetto sarebbe creare una classe figlia di lista e aggiungere la funzione degli indici come metodo di classe. In questo modo sarebbe necessario solo il metodo del filtro:

class MyList(list):
    def __init__(self, *args):
        list.__init__(self, *args)
    def indices(self, filtr=lambda x: bool(x)):
        return [i for i,x in enumerate(self) if filtr(x)]

my_list = MyList([1,0,3,5,1])
my_list.indices(lambda x: x==1)

Ho elaborato un po 'di più su questo argomento qui: http://tinyurl.com/jajrr87

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.