2010-10-01 28 views
19

quiero hacer exactamente lo que este hombre hizo:detectar eficientemente inscripciones cambios en pitón

Python - count sign changes

Sin embargo tengo que optimizarlo para correr muy rápido. En resumen, quiero tomar una serie temporal y decir cada vez que cruza las cruces cero (cambia de signo). Quiero registrar el tiempo entre cruces cero. Como se trata de datos reales (flotación de 32 bits), dudo que cada uno tenga un número que sea exactamente cero, por lo que no es importante. Actualmente tengo un programa de sincronización en el lugar así que voy a medir el tiempo de tus resultados para ver quién gana.

Mi solución da (micro segundos):

open data  8384 
sign data  8123 
zcd data  415466 

Como se puede ver el detector de cruce por cero es la parte lenta. Aquí está mi código.

import numpy, datetime 

class timer(): 
    def __init__(self): 
     self.t0 = datetime.datetime.now() 
     self.t = datetime.datetime.now() 
    def __call__(self,text='unknown'): 
     print text,'\t',(datetime.datetime.now()-self.t).microseconds 
     self.t=datetime.datetime.now() 

def zcd(data,t): 
    sign_array=numpy.sign(data) 
    t('sign data') 
    out=[] 
    current = sign_array[0] 
    count=0 
    for i in sign_array[1:]: 
     if i!=current: 
      out.append(count) 
      current=i 
      count=0 
     else: count+=1 
    t('zcd data') 
    return out 

def main(): 
    t = timer() 
    data = numpy.fromfile('deci.dat',dtype=numpy.float32) 
    t('open data') 
    zcd(data,t) 

if __name__=='__main__': 
    main() 
+2

Hay un módulo 'timeit', ¿sabes? :) –

+0

Interesante ... Me gusta más la mía porque se puede poner en una función. Puede soltar un t() cada par de líneas y encontrar cuellos de botella rápidamente. Si tan solo quisiera sincronizar mi función, habría utilizado el linux '$ time python zcd.py' – chriscauley

+0

Supongo que la línea' time ('data') 'significa' t ('sign data') ' . ¿Lo es? –

Respuesta

46

¿Qué hay de:

import numpy 
a = [1, 2, 1, 1, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10] 
zero_crossings = numpy.where(numpy.diff(numpy.sign(a)))[0] 

de salida:

> zero_crossings 
array([ 3, 5, 9, 10, 11, 12, 15]) 

es decir zero_crossings contendrá los índices de elementos después de que se produce un cruce por cero. Si desea los elementos antes de, simplemente agregue 1 a esa matriz.

+1

Creo que lo tienes al revés; 'zero_crossings' contiene los lidices de elementos _antes de que_ se produzca un cruce por cero, y si desea los elementos _después_ agrega 1 a la matriz. De lo contrario, respuesta excelente y conciso! – staticfloat

+10

Esto no funciona cuando hay un cero en la matriz. ¡Los detectará dos veces! Ejemplo: 'a = [2,1,0, -1,2]' dará 'array ([1, 2, 3])' – YuppieNetworking

3

¿Quieres cronometrarlo? ¿O quieres hacerlo lo más rápido posible?

La sincronización es fácil. Ejecuta un trillón de veces, cronómetro y divide por un trillón.

Para hacerlo lo más rápido posible, lo que necesita hacer es descubrir lo que lleva tiempo y lo que podría hacer de una mejor manera. Uso ya sea 1) la técnica de pausa aleatoria, o 2) la técnica de un solo paso.

respuesta
+0

+1 pausa aleatoria es genial – knitti

+0

La sincronización es fácil y funciona lo suficientemente rápido puede obtener una hora exacta ejecutándola una vez. Quiero que el script se ejecute rápido porque es parte de un procesador de datos en tiempo semi real. – chriscauley

+1

@dustynachos: FWIW, aquí hay una cuenta paso a paso del uso de pausa aleatoria para obtener una serie de aceleraciones que se combinan a más de 40x. http://stackoverflow.com/questions/926266/performance-optimization-strategies-of-last-resort/927773#927773 –

8

Jim Brissom falla si un contiene el valor 0:

import numpy 
a2 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10] 
zero_crossings2 = numpy.where(numpy.diff(numpy.sign(a2)))[0] 
print zero_crossings2 
print len(zero_crossings2) # should be 7 

Salida:

[ 3 4 6 10 11 12 13 16] 
8 

El número de cruce por cero debe ser de 7, pero debido signo() devuelve 0 si Se pasa 0, 1 para positivo y -1 para valores negativos, diff() contará la transición que contiene cero dos veces.

Una alternativa podría ser:

a3 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, 0, -2, 0, 0, 1, 0, -3, 0, 5, 6, 7, -10] 
s3= numpy.sign(a3) 
s3[s3==0] = -1  # replace zeros with -1 
zero_crossings3 = numpy.where(numpy.diff(s3))[0] 
print s3 
print zero_crossings3 
print len(zero_crossings3) # should be 7 

que dan la respuesta correcta de:

[ 3 6 10 14 15 18 21] 
7 
+0

Gracias - Acabo de encontrarme con esta respuesta. Me pregunto si hay una manera fácil de conocer el ** "signo" ** del cruce por cero (¿ir por encima de 0 o ir por debajo de 0)? La pendiente probablemente debería ayudar. –

+1

esto no se ocupa del caso donde los elementos antes y después de un 0 son del mismo signo. – skyork

+0

En lugar de usar 'numpy.sign', que devuelve -1, 0 o 1 para negativo, cero o positivo, debería simplemente usar' numpy.where (numpy.diff (a2> 0)) [0] '. O use la respuesta de Dominik Neise, 'np.signbit'. – IceArdor

7

Otra manera de contar los cruces por cero y apretar sólo un poco más de milisegundos a cabo del código es utilizar nonzero y calcule los signos directamente. Asumiendo que tiene una matriz unidimensional de data:

def crossings_nonzero_all(data): 
    pos = data > 0 
    npos = ~pos 
    return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0] 

Alternativamente, si lo que desea es contar los cruces por cero para una dirección de cruce de cero (por ejemplo,, De positivo a negativo), esto es incluso más rápido:

def crossings_nonzero_pos2neg(data): 
    pos = data > 0 
    return (pos[:-1] & ~pos[1:]).nonzero()[0] 

En mi máquina éstos son un poco más rápido que los where(diff(sign)) método (temporizaciones para una matriz de 10000 muestras sinusoidales que contienen 20 ciclos, 40 cruces en todo):

$ python -mtimeit 'crossings_where(data)' 
10000 loops, best of 3: 119 usec per loop 

$ python -mtimeit 'crossings_nonzero_all(data)' 
10000 loops, best of 3: 61.7 usec per loop 

$ python -mtimeit 'crossings_nonzero_pos2neg(data)' 
10000 loops, best of 3: 55.5 usec per loop 
20

Como comentó Jay Borseth, la respuesta aceptada no maneja las matrices que contienen 0 correctamente.

Propongo usando:

import numpy as np 
a = np.array([-2, -1, 0, 1, 2]) 
zero_crossings = np.where(np.diff(np.signbit(a)))[0] 
print(zero_crossings) 
# output: [1] 

Desde a) utilizar numpy.signbit() es un poco más rápido que numpy.sign(), ya que es la aplicación más simple es, supongo, y b) se ocupa correctamente con ceros en la matriz de entrada.

Sin embargo hay un inconveniente, tal vez: Si la entrada de matriz inicia y se detiene con ceros, encontrará un cruce por cero al comienzo, pero no al final ...

import numpy as np 
a = np.array([0, -2, -1, 0, 1, 2, 0]) 
zero_crossings = np.where(np.diff(np.signbit(a)))[0] 
print(zero_crossings) 
# output: [0 2] 
+1

Hmmm, ¿qué hay de $ [- 2, -1,0, -1, -2,0] $ ... no hay cruces solo conmovedores, pero una respuesta. Sin embargo, contar cero como positivo tampoco es la solución final, supongo. – mikuszefski

+0

@mikuszefski ¡Tienes razón! '[1, 2, 0, -1, 0, 0, -1, 2]' debería producir '2' cruces por cero, lo que no ocurre. –

1

veo a la gente usando diff en sus soluciones, pero xor parece ser mucho más rápido y el resultado es el mismo para bools (un buen indicador de eso también podría ser el hecho de que el uso de diff da una advertencia obsoleta .... :)) Aquí es un ejemplo:

positive = a2 > 0 
np.where(np.bitwise_xor(positive[1:], positive[:-1]))[0] 

Tiempo lo mide t o sea alrededor de un año y medio más rápido para diff para mí :)

Si no se preocupan por casos extremos podría ser mejor usar

positive = np.signbit(a2) 

pero = a2 positivos> 0 parece más rápido (y más limpio) que firmar Y verificar 0 (por ejemplo, positive = np.bitwise_or (np.signbit (a2), np.logical_not (a2)) es más lento ...)

0

Otra forma que podría adaptarse a ciertas aplicaciones es ampliar la evaluación de la expresión np.diff(np.sign(a)).

Si comparamos cómo esta expresión reacciona a ciertos casos:

  1. Rising cruce sin cero: np.diff(np.sign([-10, 10])) devuelve array([2])
  2. Rising cruce con cero: np.diff(np.sign([-10, 0, 10])) devuelve array([1, 1])
  3. La caída de cruce sin cero: np.diff(np.sign([10, -10])) devuelve array([-2])
  4. Falling crossing with zero: np.diff(np.sign([10, 0, -10])) returns array([-1, -1])

así que tenemos que evaluar np.diff(...) de los patrones devueltos en 1. y 2:

sdiff = np.diff(np.sign(a)) 
rising_1 = (sdiff == 2) 
rising_2 = (sdiff[:-1] == 1) & (sdiff[1:] == 1) 
rising_all = rising_1 
rising_all[1:] = rising_all[1:] | rising_2 

y para los casos 3. y 4.:

falling_1 = (sdiff == -2) #the signs need to be the opposite 
falling_2 = (sdiff[:-1] == -1) & (sdiff[1:] == -1) 
falling_all = falling_1 
falling_all[1:] = falling_all[1:] | falling_2 

Después de esto podemos encontrar fácilmente los índices con

indices_rising = np.where(rising_all)[0] 
indices_falling = np.where(falling_all)[0] 
indices_both = np.where(rising_all | falling_all)[0] 

Este enfoque debe ser razonable rápido, ya que puede manejar sin necesidad de utilizar un bucle "lento".

Esto combina el enfoque de varias otras respuestas.

Cuestiones relacionadas