suavizar datos de tiempo muestreados irregularmente
-
06-07-2019 - |
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
-
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)
-
Enorme ~ 1e6 - 10e6 líneas + necesitan trabajar con RAM apretada
-
Los datos son aproximadamente aleatorios
-
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.
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.