Recherche de l'index des éléments en fonction d'une condition à l'aide de la compréhension de liste python

119

Le code Python suivant semble être très long lorsqu'il vient d'un arrière-plan Matlab

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

Quand dans Matlab je peux écrire:

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

Existe-t-il une méthode courte pour écrire ceci en Python, ou est-ce que je m'en tiens simplement à la version longue?


Merci pour toutes les suggestions et explications sur la justification de la syntaxe de Python.

Après avoir trouvé ce qui suit sur le site numpy, je pense avoir trouvé une solution que j'aime:

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

L'application des informations de ce site Web à mon problème ci-dessus donnerait ce qui suit:

>>> 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]

Ce qui suit devrait alors fonctionner (mais je n'ai pas d'interpréteur Python sous la main pour le tester):

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]
Lee
la source
6
Et pourquoi pas [idx for idx in range(len(a)) if a[idx] > 2]? La raison pour laquelle cela est un peu difficile à faire en Python est qu'il n'utilise pas autant les index que les autres langages.
NullUserException

Réponses:

77
  • En Python, vous n'utiliseriez pas du tout d'index pour cela, mais vous traitiez simplement des valeurs— [value for value in a if value > 2]. Habituellement, traiter des index signifie que vous ne faites pas quelque chose de la meilleure façon.

  • Si vous avez besoin d' une API similaire à Matlab de, vous pouvez utiliser numpy , un paquet pour les tableaux multidimensionnels et mathématiques numériques en Python qui est fortement inspiré par Matlab. Vous utiliseriez un tableau numpy au lieu d'une liste.

    >>> 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])
Mike Graham
la source
2
vous avez des listes, une pour les plages et une pour les angles, vous voulez filtrer les valeurs de plage qui sont au-dessus d'un certain seuil. Comment filtrer également les angles correspondant à ces plages de manière «optimale»?
Mehdi
3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham
7
"En Python, vous n'utiliseriez pas du tout d'index pour cela, mais traitez simplement les valeurs." Cette déclaration montre que vous n'avez pas fait suffisamment d'analyse des données et de modélisation d'apprentissage automatique. Les indices d'un tenseur basés sur certaines conditions sont utilisés pour filtrer un autre tenseur.
horaceT
63

Autrement:

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

En général, rappelez-vous que si findc'est une fonction prête à l'emploi, la compréhension de liste est une solution générale et donc très puissante . Rien ne vous empêche d'écrire une findfonction en Python et de l'utiliser plus tard comme vous le souhaitez. C'est à 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]

Notez que j'utilise des listes ici pour imiter Matlab. Il serait plus pythonique d'utiliser des générateurs et des itérateurs.

Eli Bendersky
la source
2
Le PO aurait pu l'écrire comme à la [i for i,v in enumerate(a) if v > 2]place.
NullUserException
Ce n'est pas plus court, c'est plus long. Remplacez indexpar iet valuepar vdans l'original et comptez les caractères.
agf
@NullUser, agf: vous avez raison, mais le point principal est la deuxième partie :)
Eli Bendersky
1
Utiliser enumerateover range(len(...))est à la fois plus robuste et plus efficace.
Mike Graham
1
@Mike Graham: Je suis d'accord - je changerai la find_indicesfonction à utiliserenumerate
Eli Bendersky
22

Pour moi, cela fonctionne bien:

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

Peut-être qu'une autre question est: "qu'allez-vous faire de ces indices une fois que vous les aurez?" Si vous prévoyez de les utiliser pour créer une autre liste, alors en Python, ils constituent une étape intermédiaire inutile. Si vous voulez que toutes les valeurs correspondent à une condition donnée, utilisez simplement le filtre intégré:

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

Ou rédigez votre propre liste de compréhension:

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

Si vous souhaitez les supprimer de la liste, la méthode pythonique n'est pas nécessairement de les supprimer de la liste, mais d'écrire une compréhension de liste comme si vous créiez une nouvelle liste et la réassigniez en place en utilisant le listvar[:]sur la gauche. -côté:

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

Matlab fournit findcar son modèle centré sur les tableaux fonctionne en sélectionnant des éléments à l'aide de leurs indices de tableau. Vous pouvez le faire en Python, certes, mais la manière la plus pythonique consiste à utiliser des itérateurs et des générateurs, comme déjà mentionné par @EliBendersky.

PaulMcG
la source
Paul, je n'ai pas encore rencontré de besoin pour cela dans un script / une fonction / une classe. C'est plus pour le test interactif d'une classe que j'écris.
Lee
@Mike - merci pour la modification, mais je voulais vraiment dire a[:] = ...- voir la réponse d'Alex Martelli à cette question stackoverflow.com/questions/1352885/… .
PaulMcG
@Paul, j'ai supposé (et espéré!) Que votre description ne voulait pas vraiment dire que vous alliez "créer une nouvelle liste"; Je trouve que les programmes ont tendance à être plus faciles à comprendre et à maintenir lorsqu'ils modifient les données existantes avec parcimonie. Dans tous les cas, je suis désolé d'avoir outrepassé - vous devriez certainement être en mesure de modifier votre message comme vous le souhaitez.
Mike Graham le
6

Même si c'est une réponse tardive: je pense que c'est toujours une très bonne question et IMHO Python (sans bibliothèques supplémentaires ou boîtes à outils comme numpy) manque toujours d'une méthode pratique pour accéder aux index des éléments de la liste selon un filtre défini manuellement.

Vous pouvez définir manuellement une fonction, qui fournit cette fonctionnalité:

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))

Rendements: [0, 4]

Dans mon imagination, le moyen idéal serait de créer une classe enfant de liste et d'ajouter la fonction index comme méthode de classe. De cette façon, seule la méthode de filtrage serait nécessaire:

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)

J'ai élaboré un peu plus sur ce sujet ici: http://tinyurl.com/jajrr87

Gerhard Hagerer
la source