2009-06-21 10 views
5

Dada una tabla donde la primera columna es segundo allá de un cierto punto de referencia y el segundo es una medida arbitraria:de suavizado datos de tiempo irregular muestreados

6 0.738158581 
21 0.801697222 
39 1.797224596 
49 2.77920469 
54 2.839757536 
79 3.832232283 
91 4.676794376 
97 5.18244704 
100 5.521878863 
118 6.316630137 
131 6.778507504 
147 7.020395216 
157 7.331607129 
176 7.637492223 
202 7.848079136 
223 7.989456499 
251 8.76853608 
278 9.092367123 
    ... 

Como se ve, las mediciones se toman muestras en los puntos de tiempo irregulares . Necesito suavizar los datos promediando la lectura hasta 100 segundos antes de cada medición (en Python). Dado que la tabla de datos es enorme, se prefiere realmente un método basado en iteradores. Desafortunadamente, después de dos horas de codificación no puedo encontrar una solución eficiente y elegante.

¿Alguien me puede ayudar?

EDITAR s

  1. quiero una lectura suavizada para cada lectura cruda, y la lectura de alisado es igual a la media aritmética de la lectura primas y cualquier otro en los 100 segundos (delta) anteriores . (Juan, tienes razón)

  2. Enormes ~ 1e6 - líneas 10E6 + necesidad de trabajar con la memoria RAM apretada

  3. Los datos son aproximadamente paseo aleatorio

  4. los datos se clasifican

RESOLUCIÓN

He probado soluciones propuestas por J Machin y yairchu. Ambos dieron los mismos resultados, sin embargo, en mi conjunto de datos, la versión de J Machin se desempeña exponencialmente, mientras que la de yairchu es lineal. A continuación se presentan los tiempos de ejecución según lo medido por % timeit de IPython (en microsegundos):

data size J Machin yairchu 
10  90.2  55.6 
50   930   258 
100   3080  514 
500   64700  2660 
1000  253000  5390 
2000  952000  11500 

Gracias a todos por la ayuda.

+1

¿es demasiado grande para manipularlo en matrices numpy? ¿Cuántos artículos tienes? –

+0

¿Es esta interpolación lineal para encontrar puntos que son múltiplos de 100? –

+0

Si tiene requisitos de suavizado, por favor elabore un poco más. Intenté un par de veces, pero no puedo analizar esta descripción: "Necesito suavizar los datos promediando la lectura hasta 100 segundos antes de cada medición". – rix0rrr

Respuesta

2

Estoy usando un resultado de suma al cual estoy agregando los nuevos miembros y restando los antiguos. Sin embargo, de esta manera uno puede sufrir la acumulación de imprecisiones en coma flotante.

Por lo tanto, implemento un "Deque" con una lista. Y cada vez que mi Deque se reasigna a un tamaño más pequeño. Recalculé la suma en la misma ocasión.

También estoy calculando el promedio hasta el punto x incluyendo el punto x, por lo que hay al menos un punto de muestra para promediar.

def getAvgValues(data, avgSampleTime): 
    lastTime = 0 
    prevValsBuf = [] 
    prevValsStart = 0 
    tot = 0 
    for t, v in data: 
    avgStart = t - avgSampleTime 
    # remove too old values 
    while prevValsStart < len(prevValsBuf): 
     pt, pv = prevValsBuf[prevValsStart] 
     if pt > avgStart: 
     break 
     tot -= pv 
     prevValsStart += 1 
    # add new item 
    tot += v 
    prevValsBuf.append((t, v)) 
    # yield result 
    numItems = len(prevValsBuf) - prevValsStart 
    yield (t, tot/numItems) 
    # clean prevVals if it's time 
    if prevValsStart * 2 > len(prevValsBuf): 
     prevValsBuf = prevValsBuf[prevValsStart:] 
     prevValsStart = 0 
     # recalculate tot for not accumulating float precision error 
     tot = sum(v for (t, v) in prevValsBuf) 
+0

(1) Esa es una implementación muy interesante de un deque. (2) Dudo que el OP esté muy preocupado por la acumulación de errores de redondeo de coma flotante; seguramente serían cortes muy pequeños en comparación con los severos cambios del alisamiento ... pero si lo es, se podría considerar usar un sumador de Kahan para mantener el total acumulado. –

+0

Tenga en cuenta que esto es muy computacionalmente intenso en comparación con una media móvil exponencial (http://stackoverflow.com/questions/1023860/exponential-moving-average-sampled-at-varying-times/1024008). A menos que necesites especialmente asegurarte de que todas las muestras dentro del rango de tiempo contribuyan por igual, y las más antiguas no contribuyen en absoluto, iría con la EMA mucho más eficiente. –

+0

@Curt Sampson: el OP particularmente pidió esto – yairchu

-1

¿qué tal algo así, seguir almacenando valores hasta la diferencia de tiempo con la última vez es> 100, promedio y cede esos valores p.

def getAvgValues(data): 
    lastTime = 0 
    prevValues = [] 
    avgSampleTime=100 

    for t, v in data: 
     if t - lastTime < avgSampleTime: 
      prevValues.append(v) 
     else: 
      avgV = sum(prevValues)/len(prevValues) 
      lastTime = t 
      prevValues = [v] 
      yield (t,avgV) 

for v in getAvgValues(data): 
    print v 
+0

pidió promedios de los 100 segundos anteriores para todos los tiempos de mediciones originales. usted solo da 2 resultados para su ejemplo – yairchu

+0

hmm lo malentendí, de alguna manera parece que lo modificó para la solución correcta –

+0

No lo modifiqué realmente. Acabo de usar tus nombres de variable. – yairchu

2

No ha dicho exactamente cuándo desea la salida. Supongo que quiere una lectura suavizada para cada lectura en bruto, y la lectura suavizada debe ser la media aritmética de la lectura en bruto y cualquier otra en los 100 (delta) segundos previos.

Respuesta corta: use un collections.deque ... nunca tendrá más de "delta" segundos de lecturas. La forma en que lo configuré puede tratar el deque como una lista, y calcular fácilmente la media o algún gizmoid elegante que otorgue más peso a las lecturas recientes.

Respuesta larga:

>>> the_data = [tuple(map(float, x.split())) for x in """\ 
... 6  0.738158581 
... 21  0.801697222 
[snip] 
... 251  8.76853608 
... 278  9.092367123""".splitlines()] 
>>> import collections 
>>> delta = 100.0 
>>> q = collections.deque() 
>>> for t, v in the_data: 
...  while q and q[0][0] <= t - delta: 
...   # jettison outdated readings 
...   _unused = q.popleft() 
...  q.append((t, v)) 
...  count = len(q) 
...  print t, sum(item[1] for item in q)/count, count 
... 
... 
6.0 0.738158581 1 
21.0 0.7699279015 2 
39.0 1.112360133 3 
49.0 1.52907127225 4 
54.0 1.791208525 5 
79.0 2.13137915133 6 
91.0 2.49500989771 7 
97.0 2.8309395405 8 
100.0 3.12993279856 9 
118.0 3.74976297144 9 
131.0 4.41385300278 9 
147.0 4.99420529389 9 
157.0 5.8325615685 8 
176.0 6.033109419 9 
202.0 7.15545189083 6 
223.0 7.4342562845 6 
251.0 7.9150342134 5 
278.0 8.4246097095 4 
>>> 

Editar

uno de la tienda: llegar a su fantasía gizmoid aquí.Aquí está el código:

numerator = sum(item[1] * upsilon ** (t - item[0]) for item in q) 
denominator = sum(upsilon ** (t - item[0]) for item in q) 
gizmoid = numerator/denominator 

donde Upsilon debería ser un poco inferior a 1,0 (< = cero es ilegal, justo por encima de cero hace poco suavizado, se le consigue la media aritmética más el tiempo de CPU desperdiciado, y mayor que uno da el inverso de tu propósito).

+0

Me parece que una lista regular funcionaría aquí, usando .pop (0) en lugar de .popleft(). ¿Cuál es la ventaja de collections.deque? – Paul

+2

que aparece a la izquierda de una lista de Python es O (N); haciendo clic en la izquierda de una deque es O (1) –

0

Sus datos parece ser más o menos lineal:

Plot of your data http://rix0r.nl/~rix0r/share/shot-20090621.144851.gif

¿Qué tipo de suavizado está buscando? ¿Un ajuste por mínimos cuadrados de una línea a este conjunto de datos? ¿Algún tipo de filtro de paso bajo? ¿O algo mas?

Cuéntanos la aplicación para que podamos aconsejarte un poco mejor.

EDITAR: Por ejemplo, dependiendo de la aplicación, interpolar una línea entre el primer y el último punto puede ser suficiente para sus propósitos.

+0

Esta vez puede ser lineal. – Nosredna

0

Ésta hace que sea lineal:

def process_data(datafile): 
    previous_n = 0 
    previous_t = 0 
    for line in datafile: 
     t, number = line.strip().split() 
     t = int(t) 
     number = float(number) 
     delta_n = number - previous_n 
     delta_t = t - previous_t 
     n_per_t = delta_n/delta_t 
     for t0 in xrange(delta_t): 
      yield previous_t + t0, previous_n + (n_per_t * t0) 
     previous_n = n 
     previous_t = t 

f = open('datafile.dat') 

for sample in process_data(f): 
    print sample 
+0

(1) El .strip() es redundante. (2) Parece que se olvidó de actualizar previous_ * cada vez que lo hace (3) Incluso entonces, no es aparente lo que se supone que debe hacer ... parecería hacer una interpolación lineal entre la lectura anterior y la lectura actual, a intervalos de un segundo: una interpretación interesante de los requisitos del OP. (3) Creo que quería decir 'para t0 en xrange (1, delta_t + 1)' –

-2

Suena como que necesita una simple fórmula de redondeo. Para redondear un número a un intervalo arbitrario:

redonda (num/intervalo) * Intervalo de

Puede sustituir redonda con suelo o techo para "que conduce a" o "ya que" afecta. Puede funcionar en cualquier idioma, incluido SQL.

0

O (1) memoria en caso de que pueda iterar la entrada más de una vez; puede usar un iterador para "izquierda" y uno para "derecha".

def getAvgValues(makeIter, avgSampleTime): 
    leftIter = makeIter() 
    leftT, leftV = leftIter.next() 
    tot = 0 
    count = 0 
    for rightT, rightV in makeIter(): 
    tot += rightV 
    count += 1 
    while leftT <= rightT - avgSampleTime: 
     tot -= leftV 
     count -= 1 
     leftT, leftV = leftIter.next() 
    yield rightT, tot/count 
+1

Supongo que el OP desea mostrar el valor suavizado en tiempo real ... piense en el monitor de latidos cardíacos en la sala de cuidados intensivos. –

0

Mientras que da un promedio de disminución exponencial, en lugar de un medio total, creo que es posible que desee lo que he llamado una exponential moving average with varying alpha, que es realmente un filtro de paso bajo de un solo polo. Ahora hay una solución a esa pregunta, y se ejecuta en el tiempo lineal a la cantidad de puntos de datos. Mira si te funciona.

Cuestiones relacionadas