Moving average or running mean

后端 未结 27 1340
庸人自扰
庸人自扰 2020-11-22 08:37

Is there a SciPy function or NumPy function or module for Python that calculates the running mean of a 1D array given a specific window?

27条回答
  •  一个人的身影
    2020-11-22 08:57

    From reading the other answers I don't think this is what the question asked for, but I got here with the need of keeping a running average of a list of values that was growing in size.

    So if you want to keep a list of values that you are acquiring from somewhere (a site, a measuring device, etc.) and the average of the last n values updated, you can use the code bellow, that minimizes the effort of adding new elements:

    class Running_Average(object):
        def __init__(self, buffer_size=10):
            """
            Create a new Running_Average object.
    
            This object allows the efficient calculation of the average of the last
            `buffer_size` numbers added to it.
    
            Examples
            --------
            >>> a = Running_Average(2)
            >>> a.add(1)
            >>> a.get()
            1.0
            >>> a.add(1)  # there are two 1 in buffer
            >>> a.get()
            1.0
            >>> a.add(2)  # there's a 1 and a 2 in the buffer
            >>> a.get()
            1.5
            >>> a.add(2)
            >>> a.get()  # now there's only two 2 in the buffer
            2.0
            """
            self._buffer_size = int(buffer_size)  # make sure it's an int
            self.reset()
    
        def add(self, new):
            """
            Add a new number to the buffer, or replaces the oldest one there.
            """
            new = float(new)  # make sure it's a float
            n = len(self._buffer)
            if n < self.buffer_size:  # still have to had numbers to the buffer.
                self._buffer.append(new)
                if self._average != self._average:  # ~ if isNaN().
                    self._average = new  # no previous numbers, so it's new.
                else:
                    self._average *= n  # so it's only the sum of numbers.
                    self._average += new  # add new number.
                    self._average /= (n+1)  # divide by new number of numbers.
            else:  # buffer full, replace oldest value.
                old = self._buffer[self._index]  # the previous oldest number.
                self._buffer[self._index] = new  # replace with new one.
                self._index += 1  # update the index and make sure it's...
                self._index %= self.buffer_size  # ... smaller than buffer_size.
                self._average -= old/self.buffer_size  # remove old one...
                self._average += new/self.buffer_size  # ...and add new one...
                # ... weighted by the number of elements.
    
        def __call__(self):
            """
            Return the moving average value, for the lazy ones who don't want
            to write .get .
            """
            return self._average
    
        def get(self):
            """
            Return the moving average value.
            """
            return self()
    
        def reset(self):
            """
            Reset the moving average.
    
            If for some reason you don't want to just create a new one.
            """
            self._buffer = []  # could use np.empty(self.buffer_size)...
            self._index = 0  # and use this to keep track of how many numbers.
            self._average = float('nan')  # could use np.NaN .
    
        def get_buffer_size(self):
            """
            Return current buffer_size.
            """
            return self._buffer_size
    
        def set_buffer_size(self, buffer_size):
            """
            >>> a = Running_Average(10)
            >>> for i in range(15):
            ...     a.add(i)
            ...
            >>> a()
            9.5
            >>> a._buffer  # should not access this!!
            [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
    
            Decreasing buffer size:
            >>> a.buffer_size = 6
            >>> a._buffer  # should not access this!!
            [9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
            >>> a.buffer_size = 2
            >>> a._buffer
            [13.0, 14.0]
    
            Increasing buffer size:
            >>> a.buffer_size = 5
            Warning: no older data available!
            >>> a._buffer
            [13.0, 14.0]
    
            Keeping buffer size:
            >>> a = Running_Average(10)
            >>> for i in range(15):
            ...     a.add(i)
            ...
            >>> a()
            9.5
            >>> a._buffer  # should not access this!!
            [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
            >>> a.buffer_size = 10  # reorders buffer!
            >>> a._buffer
            [5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
            """
            buffer_size = int(buffer_size)
            # order the buffer so index is zero again:
            new_buffer = self._buffer[self._index:]
            new_buffer.extend(self._buffer[:self._index])
            self._index = 0
            if self._buffer_size < buffer_size:
                print('Warning: no older data available!')  # should use Warnings!
            else:
                diff = self._buffer_size - buffer_size
                print(diff)
                new_buffer = new_buffer[diff:]
            self._buffer_size = buffer_size
            self._buffer = new_buffer
    
        buffer_size = property(get_buffer_size, set_buffer_size)
    

    And you can test it with, for example:

    def graph_test(N=200):
        import matplotlib.pyplot as plt
        values = list(range(N))
        values_average_calculator = Running_Average(N/2)
        values_averages = []
        for value in values:
            values_average_calculator.add(value)
            values_averages.append(values_average_calculator())
        fig, ax = plt.subplots(1, 1)
        ax.plot(values, label='values')
        ax.plot(values_averages, label='averages')
        ax.grid()
        ax.set_xlim(0, N)
        ax.set_ylim(0, N)
        fig.show()
    

    Which gives:

提交回复
热议问题