Pregunta

Dada una tabla donde la primera columna está segundos después de cierto punto de referencia y la segunda es una medida arbitraria:

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 puede ver, las mediciones se muestrean en puntos de tiempo irregulares. Necesito suavizar los datos promediando la lectura hasta 100 segundos antes de cada medición (en Python). Como la tabla de datos es enorme, se prefiere realmente un método basado en iterador. Desafortunadamente, después de dos horas de codificación, no puedo encontrar una solución eficiente y elegante.

¿Alguien puede ayudarme?

EDITAR s

  1. Quiero una lectura suavizada para cada lectura sin formato, y la lectura suavizada debe ser la media aritmética de la lectura sin formato y cualquier otra en los 100 segundos (delta) anteriores. (John, tienes razón)

  2. Enorme ~ 1e6 - 10e6 líneas + necesitan trabajar con RAM apretada

  3. Los datos son aproximadamente aleatorios

  4. Los datos se ordenan

RESOLUCIÓN

He probado las 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 funciona exponencialmente, mientras que la de yairchu es lineal. Los siguientes son tiempos de ejecución medidos por el % 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.

¿Fue útil?

Solución

Estoy usando un resultado de suma al que estoy agregando los nuevos miembros y restando los antiguos. Sin embargo, de esta manera uno puede sufrir la acumulación de imprecisiones de 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. Recalculo la suma en la misma ocasión.

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

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)

Otros consejos

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

Respuesta corta: use un collections.deque ... nunca tendrá más de " delta " segundos de lecturas. De la forma en que lo configuré, puede tratar la deque como una lista, y calcular fácilmente la media o algún artilugio elegante que da 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

Ventanilla única: obtenga su elegante artilugio 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 menor que 1.0 (< = cero es ilegal, justo por encima de cero hace poco suavizado, uno obtiene la media aritmética más el tiempo perdido de CPU, y mayor que uno da el inverso de su propósito).

Sus datos parecen ser aproximadamente lineales:

Trace sus datos http://rix0r.nl/~rix0r/share /shot-20090621.144851.gif

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

Díganos la aplicación para que podamos asesorarlo un poco mejor.

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

Este lo hace 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

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

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

Si bien proporciona un promedio de descomposición exponencial, en lugar de un promedio total, creo que es posible que desee lo que llamé un promedio móvil exponencial con alfa variable , que es realmente un filtro de paso bajo unipolar. Ahora hay una solución a esa pregunta, y se ejecuta en un tiempo lineal al número de puntos de datos. Vea si funciona para usted.

¿qué pasa con algo como esto? Siga almacenando valores hasta que la diferencia de tiempo con la última vez sea > 100, promediar y rendir tales valores por ejemplo,

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

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

ronda (num / intervalo) * intervalo

Puede sustituir ronda con piso o techo por " que conduce a " o " ya que " afectos Puede funcionar en cualquier lenguaje, incluido SQL.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top