【问题标题】:How can I create a running average of the last N items in a time series?如何创建时间序列中最后 N 个项目的运行平均值?
【发布时间】:2014-10-05 16:00:46
【问题描述】:

我的基本想法是创建一个链表,当每个新值进入时,将新值加 1/N 倍并减去第一个值的 1/N 倍,然后将指向 first 的指针移动 1 并释放与 first 相关联的内存。

这最终不会在 Python 中实现,只是为了让我的头脑清晰,我尝试用 Python 编写它,但我的实现存在缺陷。我需要一个双向链表吗?有没有更好的替代方法(不是基于链表)?

这是我目前的尝试:

class Link:
    def __init__(self,val):
        self.next = None
        self.value = val

class LinkedList:
    def __init__(self,maxlength):
        self.current_link = None
        self.maxlength = maxlength
        self.sum = 0.
        self.average = None
        self.length = 0
        self._first_link = None
    def add_link(self,val):
        new_link = Link(val)
        new_link.next = self.current_link
        self.current_link = new_link
        if self._first_link is None:
            self._first_link = self.current_link
        self.sum += val
        if self.length < self.maxlength:
            self.length += 1
        else:
            self.sum -= self._first_link.value
            self._first_link = self._first_link.next # this line is flawed
        self.average = self.sum/self.length
    def get_first(self):
        return self._first_link.value

# Main
ll = LinkedList(5)
for ii in xrange(10):
    ll.add_link(ii)
    print ii,ll.get_first(),ll.average

问题是 _first_link 被设置为一个没有下一个的值。也就是说,_first_link 被设置为添加的第一个项目,但它的下一个是 None,所以我看不到如何将它移动 1。这就是让我怀疑是否需要双向链表的原因。

如果有任何建议,我将不胜感激。

【问题讨论】:

  • 您要解决的实际问题是什么?我经常发现对这类东西使用指数移动平均线效果非常好,并且很容易以简单而高效的方式实现:en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
  • 另外,如果您的值的幅度变化很大,您的方法可能会受到数值问题的影响,因为在浮点数学中,((A+B)-A)-B 不一定为零。
  • 是的,我同意补偿求和或其他东西可能有助于数值准确性,但我并不担心(动态范围不大)。我要解决的问题只是我想计算一个时间序列中最后 1000 个数字的平均值,其中包含数千亿个值,所以我不想存储所有值在一个数组中。它甚至比指数移动平均线更简单——它只是我想要的平坦移动平均线。
  • 我的观点是指数移动平均线实际上比滑动窗口更容易实现,因为它不需要保留任何过去的值(最近的平均值除外)。

标签: python linked-list


【解决方案1】:

我认为最简单的实现是使用circular linked list(又名ring):

class Link(object):
    def __init__(self, value=0.0):
        self.next = None
        self.value = value

class LinkedRing(object):
    def __init__(self, length):
        self.sum = 0.0
        self.length = length
        self.current = Link()

        # Initialize all the nodes:
        last = self.current
        for i in xrange(length-1):  # one link is already created
            last.next = Link()
            last = last.next
        last.next = self.current  # close the ring

    def add_val(self, val):
        self.sum -= current.value
        self.sum += val
        self.current.value = val
        self.current = self.current.next

    def average(self):
        return self.sum / self.length


# Test example:
rolling_sum = LinkedRing(5)
while True:
    x = float(raw_input())
    rolling_sum.add_val(x)
    print(">> Average: %f" % rolling_sum.average())

【讨论】:

    【解决方案2】:

    您可以使用collections.deque 和保持运行平均值的数值稳定数学来实现此目的:

    import collections
    
    class AveragingBuffer(object):
        def __init__(self, maxlen):
            assert( maxlen>1)
            self.q=collections.deque(maxlen=maxlen)
            self.xbar=0.0
        def append(self, x):
            if len(self.q)==self.q.maxlen:
                # remove first item, update running average
                d=self.q.popleft()
                self.xbar=self.xbar+(self.xbar-d)/float(len(self.q))
            # append new item, update running average
            self.q.append(x)
            self.xbar=self.xbar+(x-self.xbar)/float(len(self.q))
    
    
    if __name__=="__main__":
        import scipy
        ab=AveragingBuffer(10)
        for i in xrange(32):
            ab.append(scipy.rand())
            print ab.xbar, scipy.average(ab.q), len(ab.q)
    

    【讨论】:

      【解决方案3】:

      好的,我想到了一个在 O[1] 时间内有效的解决方案。我仍然很好奇是否有人有基于链表的解决方案,但这个解决方案完全避免了 LL:

      class Recent:
          def __init__(self,maxlength):
              self.maxlength = maxlength
              self.length = 0
              self.values = [0 for ii in xrange(maxlength)]
              self.index = 0
              self.total = 0.
              self.average = 0.
          def add_val(self,val):
              last = self.values[self.index%self.maxlength]
              self.values[self.index%self.maxlength] = val
              self.total += val
              self.total -= last
              if self.length < self.maxlength:
                  self.length += 1
              self.average = self.total / self.length
              self.index += 1
          def print_vals(self):
              print ""
              for ii in xrange(self.length):
                  print ii,self.values[ii%self.maxlength]
              print "average:",self.average
      
      # Example to show it works
      rr = Recent(5)
      for ii in xrange(3):
          rr.add_val(ii)
      rr.print_vals()
      for ii in xrange(13):
          rr.add_val(ii)
      rr.print_vals()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-14
        相关资源
        最近更新 更多