Código de Python para contar el número de cruces por cero en una matriz

Estoy mirando para contar el número de veces que los valores en una matriz cambian en polaridad (EDITAR: Número de veces que los valores en una matriz cruzan cero).

Supongamos que tengo una matriz:

[80.6 120.8 -115.6 -76.1 131.3 105.1 138.4 -81.3 -95.3 89.2 -154.1 121.4 -85.1 96.8 68.2]` 

Quiero que la cuenta sea 8.

Una solución es ejecutar un bucle y verificar que sea mayor que o menor que 0, y mantener un historial de la polaridad anterior.

¿Podemos hacer esto más rápido?

EDIT: Mi propósito es realmente encontrar algo más rápido, porque tengo estas matrices de longitud alrededor de 68554308, y tengo que hacer estos cálculos en más de 100 matrices de este tipo.

Esto produce el mismo resultado:

 import numpy as np my_array = np.array([80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2]) ((my_array[:-1] * my_array[1:]) < 0).sum() 

da:

 8 

y parece ser la solución más rápida:

 %timeit ((my_array[:-1] * my_array[1:]) < 0).sum() 100000 loops, best of 3: 11.6 µs per loop 

Comparado con el más rápido hasta ahora:

 %timeit (np.diff(np.sign(my_array)) != 0).sum() 10000 loops, best of 3: 22.2 µs per loop 

También para matrices más grandes:

 big = np.random.randint(-10, 10, size=10000000) 

esta:

 %timeit ((big[:-1] * big[1:]) < 0).sum() 10 loops, best of 3: 62.1 ms per loop 

vs:

 %timeit (np.diff(np.sign(big)) != 0).sum() 1 loops, best of 3: 97.6 ms per loop 

Aquí hay una solución numpy . Los métodos de Numpy son generalmente bastante rápidos y están bien optimizados, pero si aún no está trabajando con numpy , es probable que haya una sobrecarga al convertir la lista en una matriz numpy :

 import numpy as np my_list = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2] (np.diff(np.sign(my_list)) != 0).sum() Out[8]: 8 

Basado en la respuesta de Scott

La expresión generadora propuesta por Scott utiliza enumerate que devuelve tuplas que contienen índice y elemento de lista. El elemento de lista no se usa en la expresión en absoluto y se descarta más tarde. Así que una mejor solución en términos de tiempo sería

 sum(1 for i in range(1, len(a)) if a[i-1]*a[i]<0) 

Si su lista a es realmente enorme, el range puede arrojar una excepción. Puede reemplazarlo con itertools.islice e itertools.count .

En la versión 2.x de Python, use xrange lugar del range de Python 3. En Python 3, xrange ya no está disponible.

Creo que un bucle es una manera directa de ir:

 a = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2] def change_sign(v1, v2): return v1 * v2 < 0 s = 0 for ind, _ in enumerate(a): if ind+1 < len(a): if change_sign(a[ind], a[ind+1]): s += 1 print s # prints 8 

Podrías usar una expresión de generador pero se pone fea:

 z_cross = sum(1 for ind, val in enumerate(a) if (ind+1 < len(a)) if change_sign(a[ind], a[ind+1])) print z_cross # prints 8 

EDITAR:

@Alik señaló que para listas enormes, la mejor opción en espacio y tiempo (al menos fuera de las soluciones que hemos considerado) no es llamar a change_sign en la expresión del generador, sino simplemente:

 z_cross = sum(1 for i, _ in enumerate(a) if (i+1 < len(a)) if a[i]*a[i+1]<0) 

Parece que quieres agrupar números por su signo. Esto se podría hacer usando el método groupby :

 In [2]: l = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2] In [3]: from itertools import groupby In [5]: list(groupby(l, lambda x: x < 0)) Out[5]: [(False, ), (True, ), (False, ), (True, ), (False, ), (True, ), (False, ), (True, ), (False, )] 

Entonces deberías usar la función len que devuelve el número de grupos:

 In [7]: len(list(groupby(l, lambda x: x < 0))) Out[7]: 9 

Obviamente, habrá al menos un grupo (para una lista que no esté vacía), pero si desea contar el número de puntos, donde una secuencia cambia su polaridad, solo puede restar un grupo. No te olvides del caso de la lista vacía.

También debe tener cuidado con los elementos cero: ¿no deberían extraerse en otro grupo? Si es así, simplemente podría cambiar el argumento key (función lambda) de la función groupby .

Puedes lograrlo usando la lista de comprensión:

 myList = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2] len([x for i, x in enumerate(myList) if i > 0 and ((myList[i-1] > 0 and myList[i] < 0) or (myList[i-1] < 0 and myList[i] > 0))])