Filtrar una lista basada en una lista de booleanos

127

Tengo una lista de valores que necesito filtrar dados los valores en una lista de booleanos:

list_a = [1, 2, 4, 6]
filter = [True, False, True, False]

Genero una nueva lista filtrada con la siguiente línea:

filtered_list = [i for indx,i in enumerate(list_a) if filter[indx] == True]

lo que resulta en:

print filtered_list
[1,4]

La línea funciona pero parece (para mí) un poco exagerada y me preguntaba si había una manera más simple de lograr lo mismo.


Consejos

Resumen de dos buenos consejos dados en las respuestas a continuación:

1- No nombre una lista filtercomo lo hice porque es una función incorporada.

2- No compares cosas con las Trueque hice, if filter[idx]==True..ya que es innecesario. Solo usar if filter[idx]es suficiente.

Gabriel
fuente
3
Solo para su información, esta es una primitiva común de computación paralela llamada compactación de flujo . (Se llama 'primitivo' no porque sea simple, sino porque se usa como bloque de construcción para muchos otros algoritmos paralelos)
BlueRaja - Danny Pflughoeft
2
Algunas notas de estilo: if filter[indx] == True¿Los que no usan ==si desea comprobar la identidad con True, el uso is. De todos modos, en este caso, toda la comparación es inútil, simplemente podría usarla if filter[indx]. Por último: nunca use el nombre de un incorporado como nombre de variable / módulo (me refiero al nombre filter). Usando algo como included, para que se iflea bien ( if included[indx]).
Bakuriu

Respuestas:

184

Estas buscando itertools.compress:

>>> from itertools import compress
>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> list(compress(list_a, fil))
[1, 4]

Comparaciones de tiempos (py3.x):

>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> %timeit list(compress(list_a, fil))
100000 loops, best of 3: 2.58 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]  #winner
100000 loops, best of 3: 1.98 us per loop

>>> list_a = [1, 2, 4, 6]*100
>>> fil = [True, False, True, False]*100
>>> %timeit list(compress(list_a, fil))              #winner
10000 loops, best of 3: 24.3 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]
10000 loops, best of 3: 82 us per loop

>>> list_a = [1, 2, 4, 6]*10000
>>> fil = [True, False, True, False]*10000
>>> %timeit list(compress(list_a, fil))              #winner
1000 loops, best of 3: 1.66 ms per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v] 
100 loops, best of 3: 7.65 ms per loop

No lo use filtercomo nombre de variable, es una función incorporada.

Ashwini Chaudhary
fuente
@Mehdi Me parece que la forma de Matlab es muy poco intuitiva, pero supongo que depende de a qué estés acostumbrado.
Ian Goldby
¿Cómo puedo seleccionar [2, 6]?
Florent
Lo entiendo, list(compress(list_a, [not i for i in fill]))debería regresar[2, 6]
Florent
42

Al igual que:

filtered_list = [i for (i, v) in zip(list_a, filter) if v]

Usar zipes la forma pitónica de iterar sobre múltiples secuencias en paralelo, sin necesidad de indexación. Esto supone que ambas secuencias tienen la misma longitud (zip se detiene después de que se agota el más corto). Utilizandoitertools para un caso tan simple es un poco exagerado ...

Una cosa que haces en tu ejemplo que realmente debes dejar de hacer es comparar las cosas con True, esto generalmente no es necesario. En lugar de if filter[idx]==True: ..., simplemente puedes escribir if filter[idx]: ....

Bas Swinckels
fuente
40

Con numpy:

In [128]: list_a = np.array([1, 2, 4, 6])
In [129]: filter = np.array([True, False, True, False])
In [130]: list_a[filter]

Out[130]: array([1, 4])

o vea la respuesta de Alex Szatmary si list_a puede ser una matriz numpy pero no filtrar

Numpy generalmente también te da un gran impulso de velocidad

In [133]: list_a = [1, 2, 4, 6]*10000
In [134]: fil = [True, False, True, False]*10000
In [135]: list_a_np = np.array(list_a)
In [136]: fil_np = np.array(fil)

In [139]: %timeit list(itertools.compress(list_a, fil))
1000 loops, best of 3: 625 us per loop

In [140]: %timeit list_a_np[fil_np]
10000 loops, best of 3: 173 us per loop
Martillo
fuente
Buen punto, prefiero usar NumPysobre listdonde sea posible. Pero si necesita usar de listtodos modos, tiene (usando la NumPysolución) crear a np.arraypartir de ambas listas, usar indexación booleana y finalmente convertir la matriz a la lista con el tolist()método. Para ser precisos, debe incluir la creación de esos objetos en la comparación de tiempo. Entonces, usar itertools.compressseguirá siendo la solución más rápida.
Nerxis
17

Para hacer esto usando numpy, es decir, si tiene una matriz a, en lugar de list_a:

a = np.array([1, 2, 4, 6])
my_filter = np.array([True, False, True, False], dtype=bool)
a[my_filter]
> array([1, 4])
Alex Szatmary
fuente
3
Si convierte my_filter en una matriz booleana, puede usar la indexación booleana directa, sin necesidad de hacerlo where.
Bas Swinckels
1
filtered_list = [list_a[i] for i in range(len(list_a)) if filter[i]]
Daniel Braun
fuente
-1

Con python 3 puede usar list_a[filter]para obtener Truevalores. Para obtener Falsevalores uselist_a[~filter]

Franklin'j Gil'z
fuente