平滑不规则采样的时间数据

时间:2009-06-21 11:36:57

标签: python datetime data-mining smoothing

给定一个表,其中第一列是经过某个参考点的秒数,第二列是任意测量值:

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 
    ...

如您所见,测量是在不规则的时间点采样的。我需要通过在每次测量之前将读数平均到100秒来平滑数据(在Python中)。由于数据表很大,因此最好使用基于迭代器的方法。 不幸的是,经过两个小时的编码后,我无法找到高效优雅的解决方案。

任何人都可以帮助我吗?

修改取值

  1. 我希望每个原始读数都有一个平滑的读数,平滑的读数是原始读数的算术平均值和前100(delta)秒中的任何其他读数。 (约翰,你说得对)

  2. 巨大~1e6 - 10e6行+需要使用紧RAM

  3. 数据大致是随机游走

  4. 数据已分类

  5. 解决方案

    我测试了J Machin和yairchu提出的解决方案。他们都给出了相同的结果,但是,在我的数据集中,J Machin的版本呈指数级,而yairchu的版本是线性的。以下是由IPython的%timeit (以微秒为单位)测量的执行时间:

    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
    

    谢谢大家的帮助。

8 个答案:

答案 0 :(得分:3)

我正在使用总和结果,我正在添加新成员并减去旧成员。然而,通过这种方式,可能会出现累积的浮点不准确性。

因此我用列表实现了“Deque”。每当我的Deque重新分配到更小的尺寸。我在同一时间重新计算总和。

我也在计算点x的平均值,包括点x,因此至少有一个样本点可以平均。

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)

答案 1 :(得分:2)

您还没有准确说明何时需要输出。我假设您希望每个原始读数都有一个平滑读数,平滑读数是原始读数的算术平均值,以及之前100(delta)秒内的任何其他读数。

简短回答:使用collections.deque ......它永远不会超过读数的“delta”秒。我设置它的方式你可以像列表一样处理双端队列,并轻松计算平均值或一些花哨的gizmoid,它会给最近的读数带来更大的影响。

答案很长:

>>> 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
>>>

修改

一站式商店:在这里得到你喜欢的gizmoid。这是代码:

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

其中upilon应该小于1.0(&lt; = 0是非法的,只是在零以上几乎没有平滑,一个得到你的算术平均值加上浪费的CPU时间,而大于一个得到你的目的的倒数)。

答案 2 :(得分:0)

您的数据似乎大致是线性的:

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

你在找什么样的平滑?一条线与该数据集的最小二乘拟合?某种低通滤波器?或其他什么?

请告诉我们申请表,以便我们为您提供更好的建议。

编辑:例如,根据应用程序,在第一个点和最后一个点之间插入一条线可能足以满足您的需要。

答案 3 :(得分:0)

这个使它成为线性的:

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

答案 4 :(得分:0)

O(1)内存以防您可以多次迭代输入 - 您可以使用一个迭代器用于“左”,一个用于“右”。

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

答案 5 :(得分:0)

虽然它给出了指数衰减的平均值,而不是总平均值,但我认为你可能想要我所谓的exponential moving average with varying alpha,它实际上是一个单极点低通滤波器。现在有了这个问题的解决方案,并且它与数据点的数量成线性关系。看看它是否适合你。

答案 6 :(得分:-1)

这样的事情,保持存储值直到与上次时间的差异为> 100,平均和收益率这样的值 e.g。

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

答案 7 :(得分:-2)

听起来你需要一个简单的舍入公式。要将任意数字舍入到任意间隔:

轮(NUM /间隔)*间隔

您可以使用地板或天花板替换“导致”或“自”影响。它可以使用任何语言,包括SQL。