Di chuyển trung bình hoặc chạy trung bình


192

Có hàm SciPy hoặc hàm NumPy hoặc mô-đun cho Python để tính toán giá trị trung bình đang chạy của mảng 1D được cung cấp cho một cửa sổ cụ thể không?

Câu trả lời:


24

Đối với một giải pháp ngắn, nhanh, thực hiện toàn bộ trong một vòng lặp, không phụ thuộc, mã dưới đây hoạt động rất tốt.

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3
cumsum, moving_aves = [0], []

for i, x in enumerate(mylist, 1):
    cumsum.append(cumsum[i-1] + x)
    if i>=N:
        moving_ave = (cumsum[i] - cumsum[i-N])/N
        #can do stuff with moving_ave here
        moving_aves.append(moving_ave)

44
Nhanh?! Giải pháp này là các đơn đặt hàng có cường độ chậm hơn so với các giải pháp với Numpy.
Bart

3
Mặc dù giải pháp tự nhiên này rất tuyệt, nhưng OP đã yêu cầu chức năng numpy / scipy - có lẽ những giải pháp đó sẽ nhanh hơn đáng kể.
Demis

254

CẬP NHẬT : các giải pháp hiệu quả hơn đã được đề xuất bởi Alleojasaarim .


Bạn có thể sử dụng np.convolvecho điều đó:

np.convolve(x, np.ones((N,))/N, mode='valid')

Giải trình

Giá trị trung bình là một trường hợp của phép toán tích chập . Đối với giá trị trung bình đang chạy, bạn trượt một cửa sổ dọc theo đầu vào và tính giá trị trung bình của nội dung của cửa sổ. Đối với các tín hiệu 1D rời rạc, tích chập là điều tương tự, ngoại trừ thay vì trung bình bạn tính một tổ hợp tuyến tính tùy ý, tức là nhân mỗi phần tử với một hệ số tương ứng và cộng các kết quả. Các hệ số đó, một cho mỗi vị trí trong cửa sổ, đôi khi được gọi là hạt nhân chập . Bây giờ, giá trị trung bình số học của các giá trị N là (x_1 + x_2 + ... + x_N) / N, vì vậy hạt nhân tương ứng là (1/N, 1/N, ..., 1/N)và đó chính xác là những gì chúng ta nhận được bằng cách sử dụng np.ones((N,))/N.

Cạnh

Đối modesố np.convolvechỉ định cách xử lý các cạnh. Tôi đã chọn validchế độ ở đây vì tôi nghĩ đó là cách mà hầu hết mọi người mong đợi hoạt động có nghĩa là hoạt động, nhưng bạn có thể có những ưu tiên khác. Dưới đây là một cốt truyện minh họa sự khác biệt giữa các chế độ:

import numpy as np
import matplotlib.pyplot as plt
modes = ['full', 'same', 'valid']
for m in modes:
    plt.plot(np.convolve(np.ones((200,)), np.ones((50,))/50, mode=m));
plt.axis([-10, 251, -.1, 1.1]);
plt.legend(modes, loc='lower center');
plt.show()

Chạy các chế độ chập có nghĩa là


4
Tôi thích giải pháp này vì nó sạch (một dòng) và tương đối hiệu quả (công việc được thực hiện bên trong numpy). Nhưng "Giải pháp hiệu quả" của Alleo sử dụng numpy.cumsumcó độ phức tạp tốt hơn.
Ulrich Stern

2
@denfromufa, tôi tin rằng tài liệu này bao gồm việc thực hiện đủ tốt và nó cũng liên kết đến Wikipedia giải thích các phép toán. Xem xét trọng tâm của câu hỏi, bạn có nghĩ câu trả lời này cần sao chép chúng không?
lapis

@lapis việc sử dụng chập để di chuyển trung bình là khá bất thường và không rõ ràng. Dưới đây là lời giải thích trực quan tốt nhất mà tôi đã tìm thấy: matlabtricks.com/post-11/moving-alusive-by-convolution
denfromufa

Đối với âm mưu và các nhiệm vụ liên quan, sẽ rất hữu ích khi điền vào nó với các giá trị Không có. Đề xuất của tôi (không quá đẹp nhưng ngắn): `` `def move_alusive (x, N, fill = True): return np.concatenate ([x for x in [[none] * (N // 2 + N% 2) * điền, np.convolve (x, np.ones ((N,)) / N, mode = 'hợp lệ'), [Không] * (N // 2) * điền,] nếu len (x)]) ` `` Mã trông rất xấu trong các bình luận SO xD Tôi không muốn thêm một câu trả lời khác vì có rất nhiều nhưng bạn có thể chỉ cần sao chép và dán nó vào IDE của mình.
Chaoste

143

Giải pháp hiệu quả

Convolution tốt hơn nhiều so với cách tiếp cận đơn giản, nhưng (tôi đoán) nó sử dụng FFT và do đó khá chậm. Tuy nhiên, đặc biệt để tính toán hoạt động có nghĩa là cách tiếp cận sau hoạt động tốt

def running_mean(x, N):
    cumsum = numpy.cumsum(numpy.insert(x, 0, 0)) 
    return (cumsum[N:] - cumsum[:-N]) / float(N)

Mã để kiểm tra

In[3]: x = numpy.random.random(100000)
In[4]: N = 1000
In[5]: %timeit result1 = numpy.convolve(x, numpy.ones((N,))/N, mode='valid')
10 loops, best of 3: 41.4 ms per loop
In[6]: %timeit result2 = running_mean(x, N)
1000 loops, best of 3: 1.04 ms per loop

Lưu ý rằng numpy.allclose(result1, result2)True, hai phương pháp là tương đương. N càng lớn, sự khác biệt về thời gian càng lớn.

cảnh báo: mặc dù cumsum nhanh hơn nhưng sẽ có lỗi tăng dấu phẩy động có thể khiến kết quả của bạn không hợp lệ / không chính xác / không được chấp nhận

các ý kiến ​​đã chỉ ra vấn đề lỗi dấu phẩy động này ở đây nhưng tôi đang làm cho nó rõ ràng hơn ở đây trong câu trả lời. .

# demonstrate loss of precision with only 100,000 points
np.random.seed(42)
x = np.random.randn(100000)+1e6
y1 = running_mean_convolve(x, 10)
y2 = running_mean_cumsum(x, 10)
assert np.allclose(y1, y2, rtol=1e-12, atol=0)
  • bạn càng tích lũy được nhiều điểm hơn thì lỗi dấu phẩy động càng lớn (vì vậy 1e5 điểm là đáng chú ý, 1e6 điểm có ý nghĩa hơn, hơn 1e6 và bạn có thể muốn đặt lại các điểm tích lũy)
  • bạn có thể gian lận bằng cách sử dụng np.longdoublenhưng lỗi dấu phẩy động của bạn vẫn sẽ có ý nghĩa đối với số điểm tương đối lớn (khoảng> 1e5 nhưng phụ thuộc vào dữ liệu của bạn)
  • bạn có thể vẽ lỗi và thấy nó tăng tương đối nhanh
  • giải pháp chập chậm hơn nhưng không có độ chính xác mất điểm trôi nổi này
  • giải pháp thống nhất_filter1d nhanh hơn giải pháp cumsum này VÀ không có điểm mất chính xác này

3
Giải pháp tốt đẹp! Linh cảm của tôi numpy.convolvelà O (mn); tài liệu của nó đề cập đến việc scipy.signal.fftconvolvesử dụng FFT.
Ulrich Stern

3
Phương pháp này không xử lý các cạnh của mảng, phải không?
JoVe

6
Giải pháp hay, nhưng lưu ý rằng nó có thể bị lỗi số cho các mảng lớn, vì đến cuối mảng, bạn có thể trừ hai số lớn để thu được kết quả nhỏ.
Bas Swinckels

1
Điều này sử dụng phân chia số nguyên thay vì phân chia float: running_mean([1,2,3], 2)cho array([1, 2]). Thay thế xbằng [float(value) for value in x]thủ thuật.
ChrisW

4
Sự ổn định về số của giải pháp này có thể trở thành vấn đề nếu xchứa phao. Ví dụ: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2trả về 0.003125trong khi người ta mong đợi 0.0. Thêm thông tin: vi.wikipedia.org/wiki/Loss_of_significance
Milan

79

Cập nhật: Ví dụ dưới đây cho thấy pandas.rolling_meanchức năng cũ đã bị xóa trong các phiên bản gần đây của gấu trúc. Một tương đương hiện đại của chức năng gọi dưới đây sẽ là

In [8]: pd.Series(x).rolling(window=N).mean().iloc[N-1:].values
Out[8]: 
array([ 0.49815397,  0.49844183,  0.49840518, ...,  0.49488191,
        0.49456679,  0.49427121])

gấu trúc thích hợp cho việc này hơn NumPy hoặc SciPy. Chức năng của nó cán_mean thực hiện công việc một cách thuận tiện. Nó cũng trả về một mảng NumPy khi đầu vào là một mảng.

Rất khó để đánh bại rolling_meanhiệu năng với bất kỳ triển khai Python thuần tùy chỉnh nào. Dưới đây là một ví dụ về hiệu suất đối với hai trong số các giải pháp được đề xuất:

In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: def running_mean(x, N):
   ...:     cumsum = np.cumsum(np.insert(x, 0, 0)) 
   ...:     return (cumsum[N:] - cumsum[:-N]) / N
   ...:

In [4]: x = np.random.random(100000)

In [5]: N = 1000

In [6]: %timeit np.convolve(x, np.ones((N,))/N, mode='valid')
10 loops, best of 3: 172 ms per loop

In [7]: %timeit running_mean(x, N)
100 loops, best of 3: 6.72 ms per loop

In [8]: %timeit pd.rolling_mean(x, N)[N-1:]
100 loops, best of 3: 4.74 ms per loop

In [9]: np.allclose(pd.rolling_mean(x, N)[N-1:], running_mean(x, N))
Out[9]: True

Ngoài ra còn có các tùy chọn tốt đẹp như làm thế nào để đối phó với các giá trị cạnh.


6
Pandas cán_mean là một công cụ tốt cho công việc nhưng đã không được dùng cho các cuộc tranh luận. Trong các phiên bản Pandas trong tương lai, nó sẽ chỉ hoạt động trên loạt Pandas. Bây giờ chúng ta chuyển ở đâu cho dữ liệu mảng không phải Pandas?
Mike

5
@Mike roll_mean () không được dùng nữa, nhưng bây giờ bạn có thể sử dụng cán và nghĩa riêng: df.rolling(windowsize).mean()bây giờ hoạt động thay thế (rất nhanh tôi có thể thêm). cho chuỗi 6.000 hàng %timeit test1.rolling(20).mean()trả về 1000 vòng, tốt nhất là 3: 1,16 ms mỗi vòng
Vlox

5
@Vlox df.rolling()hoạt động đủ tốt, vấn đề là ngay cả hình thức này sẽ không hỗ trợ ndarrays trong tương lai. Để sử dụng nó, trước tiên chúng ta sẽ phải tải dữ liệu của mình vào một Dataframe Pandas. Tôi rất thích nhìn thấy chức năng này được bổ sung vào một trong hai numpyhoặc scipy.signal.
Mike

1
@Mike hoàn toàn đồng ý. Tôi đang vật lộn đặc biệt để phù hợp với gấu trúc .ewm (). Mean () tốc độ cho mảng của riêng tôi (thay vì phải tải chúng vào df trước). Ý tôi là, thật tuyệt khi nó nhanh, nhưng chỉ cảm thấy một chút lộn xộn di chuyển vào và ra khỏi dataframes quá thường xuyên.
Vlox

6
%timeit bottleneck.move_mean(x, N)nhanh hơn 3 đến 15 lần so với phương pháp cumsum và gấu trúc trên máy tính của tôi. Hãy xem điểm chuẩn của họ trong README của repo .
mab

50

Bạn có thể tính toán trung bình hoạt động với:

import numpy as np

def runningMean(x, N):
    y = np.zeros((len(x),))
    for ctr in range(len(x)):
         y[ctr] = np.sum(x[ctr:(ctr+N)])
    return y/N

Nhưng nó chậm.

May mắn thay, numpy bao gồm một hàm chập mà chúng ta có thể sử dụng để tăng tốc mọi thứ. Giá trị trung bình tương đương với tích chập xvới một vectơ Ndài, với tất cả các thành viên bằng 1/N. Việc triển khai tích hợp nhanh chóng bao gồm tạm thời bắt đầu, do đó bạn phải xóa các điểm N-1 đầu tiên:

def runningMeanFast(x, N):
    return np.convolve(x, np.ones((N,))/N)[(N-1):]

Trên máy của tôi, phiên bản nhanh nhanh hơn 20-30 lần, tùy thuộc vào độ dài của vectơ đầu vào và kích thước của cửa sổ trung bình.

Lưu ý rằng tích hợp bao gồm một 'same'chế độ có vẻ như sẽ giải quyết vấn đề nhất thời bắt đầu, nhưng nó sẽ phân chia nó giữa đầu và cuối.


Lưu ý rằng việc xóa các điểm N-1 đầu tiên vẫn để lại hiệu ứng biên ở các điểm cuối cùng. Một cách dễ dàng hơn để giải quyết vấn đề là sử dụng mode='valid'trong convolveđó không yêu cầu xử lý hậu kỳ.
lapis

1
@Pologistso - mode='valid'loại bỏ tạm thời từ cả hai đầu, phải không? Nếu len(x)=10N=4, đối với một hoạt động có nghĩa là tôi muốn có 10 kết quả nhưng validtrả về 7.
mtrw

1
Nó loại bỏ thoáng qua từ cuối, và bắt đầu không có. Chà, tôi đoán đó là vấn đề ưu tiên, tôi không cần cùng một số kết quả về chi phí để có độ dốc về 0 mà không có trong dữ liệu. BTW, đây là một lệnh để hiển thị sự khác biệt giữa các chế độ: modes = ('full', 'same', 'valid'); [plot(convolve(ones((200,)), ones((50,))/50, mode=m)) for m in modes]; axis([-10, 251, -.1, 1.1]); legend(modes, loc='lower center')(với pyplot và numpy được nhập).
lapis

runningMeanTôi có tác dụng phụ của việc tính trung bình với các số không, khi bạn đi ra khỏi mảng với x[ctr:(ctr+N)]phía bên phải của mảng.
mrgloom

runningMeanFastcũng có vấn đề hiệu ứng biên giới này.
mrgloom

21

hoặc mô-đun cho python tính toán

trong các thử nghiệm của tôi tại Tradewave.net TA-lib luôn thắng:

import talib as ta
import numpy as np
import pandas as pd
import scipy
from scipy import signal
import time as t

PAIR = info.primary_pair
PERIOD = 30

def initialize():
    storage.reset()
    storage.elapsed = storage.get('elapsed', [0,0,0,0,0,0])

def cumsum_sma(array, period):
    ret = np.cumsum(array, dtype=float)
    ret[period:] = ret[period:] - ret[:-period]
    return ret[period - 1:] / period

def pandas_sma(array, period):
    return pd.rolling_mean(array, period)

def api_sma(array, period):
    # this method is native to Tradewave and does NOT return an array
    return (data[PAIR].ma(PERIOD))

def talib_sma(array, period):
    return ta.MA(array, period)

def convolve_sma(array, period):
    return np.convolve(array, np.ones((period,))/period, mode='valid')

def fftconvolve_sma(array, period):    
    return scipy.signal.fftconvolve(
        array, np.ones((period,))/period, mode='valid')    

def tick():

    close = data[PAIR].warmup_period('close')

    t1 = t.time()
    sma_api = api_sma(close, PERIOD)
    t2 = t.time()
    sma_cumsum = cumsum_sma(close, PERIOD)
    t3 = t.time()
    sma_pandas = pandas_sma(close, PERIOD)
    t4 = t.time()
    sma_talib = talib_sma(close, PERIOD)
    t5 = t.time()
    sma_convolve = convolve_sma(close, PERIOD)
    t6 = t.time()
    sma_fftconvolve = fftconvolve_sma(close, PERIOD)
    t7 = t.time()

    storage.elapsed[-1] = storage.elapsed[-1] + t2-t1
    storage.elapsed[-2] = storage.elapsed[-2] + t3-t2
    storage.elapsed[-3] = storage.elapsed[-3] + t4-t3
    storage.elapsed[-4] = storage.elapsed[-4] + t5-t4
    storage.elapsed[-5] = storage.elapsed[-5] + t6-t5    
    storage.elapsed[-6] = storage.elapsed[-6] + t7-t6        

    plot('sma_api', sma_api)  
    plot('sma_cumsum', sma_cumsum[-5])
    plot('sma_pandas', sma_pandas[-10])
    plot('sma_talib', sma_talib[-15])
    plot('sma_convolve', sma_convolve[-20])    
    plot('sma_fftconvolve', sma_fftconvolve[-25])

def stop():

    log('ticks....: %s' % info.max_ticks)

    log('api......: %.5f' % storage.elapsed[-1])
    log('cumsum...: %.5f' % storage.elapsed[-2])
    log('pandas...: %.5f' % storage.elapsed[-3])
    log('talib....: %.5f' % storage.elapsed[-4])
    log('convolve.: %.5f' % storage.elapsed[-5])    
    log('fft......: %.5f' % storage.elapsed[-6])

các kết quả:

[2015-01-31 23:00:00] ticks....: 744
[2015-01-31 23:00:00] api......: 0.16445
[2015-01-31 23:00:00] cumsum...: 0.03189
[2015-01-31 23:00:00] pandas...: 0.03677
[2015-01-31 23:00:00] talib....: 0.00700  # <<< Winner!
[2015-01-31 23:00:00] convolve.: 0.04871
[2015-01-31 23:00:00] fft......: 0.22306

nhập mô tả hình ảnh ở đây


NameError: name 'info' is not defined. Tôi nhận được lỗi này, thưa ông.
Md. Rezwanul Haque

1
Có vẻ như chuỗi thời gian của bạn được thay đổi sau khi làm mịn, nó có hiệu quả mong muốn không?
mrgloom

@mrgloom có, cho mục đích trực quan; mặt khác, chúng sẽ xuất hiện dưới dạng một dòng trên biểu đồ; Md. Rezwanul Haque bạn có thể xóa tất cả các tham chiếu đến PAIR và thông tin; đó là những phương pháp hộp cát nội bộ cho tradewave.net hiện không còn tồn tại
litepresence

21

Để biết giải pháp sẵn sàng sử dụng, hãy xem https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Nó cung cấp trung bình chạy với flatloại cửa sổ. Lưu ý rằng phương pháp này phức tạp hơn một chút so với phương pháp xác thực tự làm đơn giản, vì nó cố gắng xử lý các vấn đề ở đầu và cuối dữ liệu bằng cách phản ánh nó (có thể hoặc không thể hoạt động trong trường hợp của bạn. ..).

Để bắt đầu, bạn có thể thử:

a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)

1
Phương pháp này dựa vào numpy.convolve, sự khác biệt chỉ trong việc thay đổi trình tự.
Alleo

10
Tôi luôn cảm thấy khó chịu bởi chức năng xử lý tín hiệu trả lại tín hiệu đầu ra có hình dạng khác với tín hiệu đầu vào khi cả đầu vào và đầu ra có cùng bản chất (ví dụ: cả hai tín hiệu tạm thời). Nó phá vỡ sự tương ứng với biến độc lập có liên quan (ví dụ: thời gian, tần suất) làm cho âm mưu hoặc so sánh không phải là vấn đề trực tiếp ... dù sao, nếu bạn chia sẻ cảm giác, bạn có thể muốn thay đổi các dòng cuối cùng của hàm được đề xuất là y = np .convolve (w / w.sum (), s, mode = 'giống nhau'); return y [window_len-1 :-( window_len-1)]
Christian O'Reilly

@ ChristianO'Reilly, bạn nên đăng nó dưới dạng một câu trả lời riêng - đó chính xác là những gì tôi đang tìm kiếm, vì tôi thực sự có hai mảng khác phải khớp với độ dài của dữ liệu được làm mịn, để vẽ, v.v. Tôi muốn biết chính xác cách bạn đã làm điều đó - là wkích thước cửa sổ và sdữ liệu?
Demis

@Demis Vui mừng các bình luận đã giúp. Thông tin thêm về hàm kết hợp numpy ở đây docs.scipy.org/doc/numpy-1.15.0/reference/generated/ Tên Một hàm tích chập ( en.wikipedia.org/wiki/Convolution ) kết hợp hai tín hiệu với nhau. Trong trường hợp này, nó kết hợp (các) tín hiệu của bạn với một cửa sổ được chuẩn hóa (tức là khu vực đơn nhất) (w / w.sum ()).
Christian O'Reilly

18

Bạn có thể sử dụng scipy.ndimage.filters.uniform_filter1d :

import numpy as np
from scipy.ndimage.filters import uniform_filter1d
N = 1000
x = np.random.random(100000)
y = uniform_filter1d(x, size=N)

uniform_filter1d:

  • đưa ra đầu ra với hình dạng numpy tương tự (tức là số điểm)
  • cho phép nhiều cách để xử lý đường viền 'reflect'là mặc định, nhưng trong trường hợp của tôi, tôi muốn'nearest'

Nó cũng khá nhanh (nhanh hơn gần 50 lần và nhanh hơn np.convolve2-5 lần so với phương pháp cumsum được đưa ra ở trên ):

%timeit y1 = np.convolve(x, np.ones((N,))/N, mode='same')
100 loops, best of 3: 9.28 ms per loop

%timeit y2 = uniform_filter1d(x, size=N)
10000 loops, best of 3: 191 µs per loop

Dưới đây là 3 chức năng cho phép bạn so sánh lỗi / tốc độ thực hiện khác nhau:

from __future__ import division
import numpy as np
import scipy.ndimage.filters as ndif
def running_mean_convolve(x, N):
    return np.convolve(x, np.ones(N) / float(N), 'valid')
def running_mean_cumsum(x, N):
    cumsum = np.cumsum(np.insert(x, 0, 0))
    return (cumsum[N:] - cumsum[:-N]) / float(N)
def running_mean_uniform_filter1d(x, N):
    return ndif.uniform_filter1d(x, N, mode='constant', origin=-(N//2))[:-(N-1)]

1
Đây là câu trả lời duy nhất có vẻ như tính đến các vấn đề biên giới (khá quan trọng, đặc biệt là khi âm mưu). Cảm ơn bạn!
Gabriel

1
tôi định hình uniform_filter1d, np.convolvevới một hình chữ nhật, và np.cumsumtheo sau np.subtract. kết quả của tôi: (1.) chập là chậm nhất. (2.) cumsum / phép trừ nhanh hơn khoảng 20-30 lần. (3.) thống_filter1d nhanh hơn khoảng 2-3 lần so với cumsum / phép trừ. người chiến thắng chắc chắn là thống nhất_filter1d.
Trevor Boyd Smith

sử dụng uniform_filter1dnhanh hơn so với cumsumgiải pháp (khoảng 2-5x). và uniform_filter1d không nhận được lỗi dấu phẩy động lớn nhưcumsum giải pháp.
Trevor Boyd Smith

15

Tôi biết đây là một câu hỏi cũ, nhưng đây là một giải pháp không sử dụng bất kỳ cấu trúc dữ liệu hoặc thư viện bổ sung nào. Đó là tuyến tính về số lượng các yếu tố của danh sách đầu vào và tôi không thể nghĩ ra cách nào khác để làm cho nó hiệu quả hơn (thực sự nếu có ai biết cách phân bổ kết quả tốt hơn, vui lòng cho tôi biết).

LƯU Ý: điều này sẽ nhanh hơn nhiều khi sử dụng một mảng numpy thay vì một danh sách, nhưng tôi muốn loại bỏ tất cả các phụ thuộc. Cũng có thể cải thiện hiệu suất bằng cách thực hiện đa luồng

Hàm giả định rằng danh sách đầu vào là một chiều, vì vậy hãy cẩn thận.

### Running mean/Moving average
def running_mean(l, N):
    sum = 0
    result = list( 0 for x in l)

    for i in range( 0, N ):
        sum = sum + l[i]
        result[i] = sum / (i+1)

    for i in range( N, len(l) ):
        sum = sum - l[i-N] + l[i]
        result[i] = sum / N

    return result

Thí dụ

Giả sử rằng chúng tôi có một danh sách data = [ 1, 2, 3, 4, 5, 6 ]mà chúng tôi muốn tính toán trung bình cán trong khoảng thời gian 3 và bạn cũng muốn một danh sách đầu ra có cùng kích thước của đầu vào (thường là như vậy).

Phần tử đầu tiên có chỉ số 0, do đó, giá trị trung bình phải được tính trên các phần tử của chỉ số -2, -1 và 0. Rõ ràng chúng ta không có dữ liệu [-2] và dữ liệu [-1] (trừ khi bạn muốn sử dụng đặc biệt điều kiện biên), vì vậy chúng tôi giả sử rằng các phần tử đó bằng 0. Điều này tương đương với phần không đệm trong danh sách, ngoại trừ chúng tôi không thực sự đệm nó, chỉ cần theo dõi các chỉ số cần đệm (từ 0 đến N-1).

Vì vậy, đối với các phần tử N đầu tiên, chúng ta chỉ cần tiếp tục cộng các phần tử trong một bộ tích lũy.

result[0] = (0 + 0 + 1) / 3  = 0.333    ==   (sum + 1) / 3
result[1] = (0 + 1 + 2) / 3  = 1        ==   (sum + 2) / 3
result[2] = (1 + 2 + 3) / 3  = 2        ==   (sum + 3) / 3

Từ các yếu tố N + 1 chuyển tiếp tích lũy đơn giản không hoạt động. chúng tôi mong đợi result[3] = (2 + 3 + 4)/3 = 3nhưng điều này khác với (sum + 4)/3 = 3.333.

Cách để tính toán giá trị chính xác là để trừ data[0] = 1từ sum+4, do đó đem lại sum + 4 - 1 = 9.

Điều này xảy ra bởi vì hiện tại sum = data[0] + data[1] + data[2], nhưng nó cũng đúng với mọi i >= Nbởi vì, trước khi trừ, sumdata[i-N] + ... + data[i-2] + data[i-1].


12

Tôi cảm thấy điều này có thể được giải quyết một cách thanh lịch bằng cách sử dụng nút cổ chai

Xem mẫu cơ bản dưới đây:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=100)
mm = bn.move_mean(a, window=5, min_count=1)
  • "Mm" là giá trị trung bình của "a".

  • "window" là số lượng mục nhập tối đa cần xem xét để di chuyển trung bình.

  • "min_count" là số lượng mục nhập tối thiểu cần xem xét để di chuyển trung bình (ví dụ: đối với một số phần tử đầu tiên hoặc nếu mảng có giá trị nan).

Phần tốt là Bottleneck giúp xử lý các giá trị nan và nó cũng rất hiệu quả.


Lib này thực sự nhanh Hàm trung bình di chuyển Python thuần túy là chậm. Bootleneck là một thư viện PyData, tôi nghĩ là ổn định và có thể nhận được sự hỗ trợ liên tục từ cộng đồng Python, vậy tại sao không sử dụng nó?
Đi vào

6

Tôi chưa kiểm tra xem nó nhanh như thế nào, nhưng bạn có thể thử:

from collections import deque

cache = deque() # keep track of seen values
n = 10          # window size
A = xrange(100) # some dummy iterable
cum_sum = 0     # initialize cumulative sum

for t, val in enumerate(A, 1):
    cache.append(val)
    cum_sum += val
    if t < n:
        avg = cum_sum / float(t)
    else:                           # if window is saturated,
        cum_sum -= cache.popleft()  # subtract oldest value
        avg = cum_sum / float(n)

1
Đây là những gì tôi sẽ làm. Bất cứ ai có thể xin vui lòng phê bình tại sao đây là một cách xấu để đi?
stag hành

1
Giải pháp python đơn giản này làm việc tốt cho tôi mà không cần numpy. Cuối cùng tôi đã đưa nó vào một lớp học để sử dụng lại.
Matthew Tschiegg

6

Câu trả lời này chứa các giải pháp sử dụng thư viện chuẩn Python cho ba kịch bản khác nhau.


Chạy trung bình với itertools.accumulate

Đây là một giải pháp Python 3.2+ hiệu quả về bộ nhớ, tính toán trung bình đang chạy trên một giá trị lặp lại bằng cách tận dụng itertools.accumulate.

>>> from itertools import accumulate
>>> values = range(100)

Lưu ý rằng valuescó thể là bất kỳ lần lặp nào, bao gồm các trình tạo hoặc bất kỳ đối tượng nào khác tạo ra các giá trị một cách nhanh chóng.

Đầu tiên, lười biếng xây dựng tổng tích lũy của các giá trị.

>>> cumu_sum = accumulate(value_stream)

Tiếp theo, enumeratetổng tích lũy (bắt đầu từ 1) và xây dựng một trình tạo mang lại tỷ lệ giá trị tích lũy và chỉ số liệt kê hiện tại.

>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))

Bạn có thể phát hành means = list(rolling_avg)nếu bạn cần tất cả các giá trị trong bộ nhớ cùng một lúc hoặc gọi nexttăng dần.
(Tất nhiên, bạn cũng có thể lặp đi lặp lại rolling_avgvới một forvòng lặp, sẽ gọi nextngầm.)

>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0

Giải pháp này có thể được viết như một chức năng như sau.

from itertools import accumulate

def rolling_avg(iterable):
    cumu_sum = accumulate(iterable)
    yield from (accu/i for i, accu in enumerate(cumu_sum, 1))
    

Một coroutine mà bạn có thể gửi các giá trị bất cứ lúc nào

Coroutine này tiêu thụ các giá trị bạn gửi nó và giữ mức trung bình hoạt động của các giá trị được nhìn thấy cho đến nay.

Nó rất hữu ích khi bạn không có các giá trị lặp lại nhưng chứa các giá trị được tính trung bình từng cái một vào các thời điểm khác nhau trong suốt cuộc đời của chương trình.

def rolling_avg_coro():
    i = 0
    total = 0.0
    avg = None

    while True:
        next_value = yield avg
        i += 1
        total += next_value
        avg = total/i
        

Các coroutine hoạt động như thế này:

>>> averager = rolling_avg_coro() # instantiate coroutine
>>> next(averager) # get coroutine going (this is called priming)
>>>
>>> averager.send(5) # 5/1
>>> 5.0
>>> averager.send(3) # (5 + 3)/2
>>> 4.0
>>> print('doing something else...')
doing something else...
>>> averager.send(13) # (5 + 3 + 13)/3
>>> 7.0

Tính toán trung bình trên một cửa sổ trượt có kích thước N

Hàm tạo này có một kích thước lặp và kích thước cửa sổ N và mang lại giá trị trung bình trên các giá trị hiện tại bên trong cửa sổ. Nó sử dụng một deque, đó là một cơ sở hạ tầng tương tự như một danh sách, nhưng được tối ưu hóa để sửa đổi nhanh ( pop, append) ở cả hai điểm cuối .

from collections import deque
from itertools import islice

def sliding_avg(iterable, N):        
    it = iter(iterable)
    window = deque(islice(it, N))        
    num_vals = len(window)

    if num_vals < N:
        msg = 'window size {} exceeds total number of values {}'
        raise ValueError(msg.format(N, num_vals))

    N = float(N) # force floating point division if using Python 2
    s = sum(window)
    
    while True:
        yield s/N
        try:
            nxt = next(it)
        except StopIteration:
            break
        s = s - window.popleft() + nxt
        window.append(nxt)
        

Đây là chức năng trong hành động:

>>> values = range(100)
>>> N = 5
>>> window_avg = sliding_avg(values, N)
>>> 
>>> next(window_avg) # (0 + 1 + 2 + 3 + 4)/5
>>> 2.0
>>> next(window_avg) # (1 + 2 + 3 + 4 + 5)/5
>>> 3.0
>>> next(window_avg) # (2 + 3 + 4 + 5 + 6)/5
>>> 4.0

5

Đến bữa tiệc muộn một chút, nhưng tôi đã tạo ra chức năng nhỏ của riêng mình mà KHÔNG quấn quanh các đầu hoặc miếng đệm bằng số không được sử dụng để tìm mức trung bình. Một điều trị nữa là, nó cũng lấy mẫu lại tín hiệu tại các điểm cách nhau tuyến tính. Tùy chỉnh mã theo ý muốn để có được các tính năng khác.

Phương thức này là một phép nhân ma trận đơn giản với một nhân Gaussian được chuẩn hóa.

def running_mean(y_in, x_in, N_out=101, sigma=1):
    '''
    Returns running mean as a Bell-curve weighted average at evenly spaced
    points. Does NOT wrap signal around, or pad with zeros.

    Arguments:
    y_in -- y values, the values to be smoothed and re-sampled
    x_in -- x values for array

    Keyword arguments:
    N_out -- NoOf elements in resampled array.
    sigma -- 'Width' of Bell-curve in units of param x .
    '''
    N_in = size(y_in)

    # Gaussian kernel
    x_out = np.linspace(np.min(x_in), np.max(x_in), N_out)
    x_in_mesh, x_out_mesh = np.meshgrid(x_in, x_out)
    gauss_kernel = np.exp(-np.square(x_in_mesh - x_out_mesh) / (2 * sigma**2))
    # Normalize kernel, such that the sum is one along axis 1
    normalization = np.tile(np.reshape(sum(gauss_kernel, axis=1), (N_out, 1)), (1, N_in))
    gauss_kernel_normalized = gauss_kernel / normalization
    # Perform running average as a linear operation
    y_out = gauss_kernel_normalized @ y_in

    return y_out, x_out

Một cách sử dụng đơn giản trên tín hiệu hình sin có thêm nhiễu phân tán bình thường: nhập mô tả hình ảnh ở đây


Điều này không làm việc cho tôi (python 3.6). 1 Không có chức năng đặt tên sum, sử dụng np.sumthay vì 2 Các @nhà điều hành (không có ý tưởng gì đó là) ném một lỗi. Tôi có thể xem xét nó sau nhưng tôi đang thiếu thời gian ngay bây giờ
Bastian

Các @là toán tử nhân ma trận mà cụ np.matmul . Kiểm tra xem y_inmảng của bạn có phải là một mảng numpy không, đó có thể là vấn đề.
xyzzyqed

5

Thay vì numpy hoặc scipy, tôi muốn đề nghị gấu trúc làm điều này nhanh hơn:

df['data'].rolling(3).mean()

Điều này lấy trung bình di chuyển (MA) của 3 giai đoạn của "dữ liệu" cột. Bạn cũng có thể tính toán các phiên bản đã thay đổi, ví dụ: phiên bản loại trừ ô hiện tại (đã dịch ngược một lần) có thể được tính toán dễ dàng như sau:

df['data'].shift(periods=1).rolling(3).mean()

Điều này khác với giải pháp đề xuất năm 2016 như thế nào?
Ông T

2
Các giải pháp đề xuất trong năm 2016 sử dụng pandas.rolling_meantrong khi sử dụng của tôi pandas.DataFrame.rolling. Bạn cũng có thể tính toán di chuyển min(), max(), sum()vv cũng như mean()với phương pháp này một cách dễ dàng.
Gursel Karacor

Trước đây bạn cần sử dụng một phương pháp khác như pandas.rolling_min, pandas.rolling_maxvv Chúng tương tự nhau nhưng khác nhau.
Gursel Karacor

4

Có một nhận xét của mab chôn trong một trong những câu trả lời ở trên có phương pháp này. bottleneckmove_meanmột trung bình di chuyển đơn giản:

import numpy as np
import bottleneck as bn

a = np.arange(10) + np.random.random(10)

mva = bn.move_mean(a, window=2, min_count=1)

min_countlà một tham số tiện dụng về cơ bản sẽ lấy trung bình di chuyển đến điểm đó trong mảng của bạn. Nếu bạn không đặt min_count, nó sẽ bằng nhau windowvà mọi thứ windowsẽ được tính điểm nan.


3

Một cách tiếp cận khác để tìm trung bình di chuyển mà không sử dụng numpy, panda

import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a, 
               enumerate(itertools.accumulate(sample), 1)))

sẽ in [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]


itertools.accumulation không tồn tại trong python 2.7, nhưng tồn tại trong python 3.4
Grayaii

3

Câu hỏi này thậm chí còn cũ hơn so với khi NeXuS viết về nó vào tháng trước, NHƯNG tôi thích cách mã của anh ấy xử lý các trường hợp cạnh. Tuy nhiên, vì là "trung bình di chuyển đơn giản", kết quả của nó bị tụt hậu so với dữ liệu họ áp dụng. Tôi nghĩ rằng đối phó với trường hợp cạnh một cách thỏa mãn hơn chế độ NumPy của valid, samefullcó thể đạt được bằng cách áp dụng một cách tiếp cận tương tự như một convolution()phương pháp dựa.

Đóng góp của tôi sử dụng trung bình chạy trung tâm để căn chỉnh kết quả của nó với dữ liệu của họ. Khi có quá ít điểm có sẵn cho cửa sổ kích thước đầy đủ được sử dụng, trung bình chạy được tính từ các cửa sổ nhỏ hơn liên tiếp ở các cạnh của mảng. [Trên thực tế, từ các cửa sổ lớn hơn liên tiếp, nhưng đó là một chi tiết triển khai.]

import numpy as np

def running_mean(l, N):
    # Also works for the(strictly invalid) cases when N is even.
    if (N//2)*2 == N:
        N = N - 1
    front = np.zeros(N//2)
    back = np.zeros(N//2)

    for i in range(1, (N//2)*2, 2):
        front[i//2] = np.convolve(l[:i], np.ones((i,))/i, mode = 'valid')
    for i in range(1, (N//2)*2, 2):
        back[i//2] = np.convolve(l[-i:], np.ones((i,))/i, mode = 'valid')
    return np.concatenate([front, np.convolve(l, np.ones((N,))/N, mode = 'valid'), back[::-1]])

Nó tương đối chậm bởi vì nó sử dụng convolve(), và có khả năng có thể được tạo ra khá nhiều bởi một Pythonista thực sự, tuy nhiên, tôi tin rằng ý tưởng đó tồn tại.


3

Có nhiều câu trả lời ở trên về việc tính toán trung bình đang chạy. Câu trả lời của tôi thêm hai tính năng bổ sung:

  1. bỏ qua các giá trị nan
  2. tính giá trị trung bình của N giá trị lân cận KHÔNG bao gồm giá trị lãi suất

Tính năng thứ hai này đặc biệt hữu ích để xác định giá trị nào khác với xu hướng chung theo một lượng nhất định.

Tôi sử dụng numpy.cumsum vì đây là phương pháp hiệu quả nhất về thời gian ( xem câu trả lời của Alleo ở trên ).

N=10 # number of points to test on each side of point of interest, best if even
padded_x = np.insert(np.insert( np.insert(x, len(x), np.empty(int(N/2))*np.nan), 0, np.empty(int(N/2))*np.nan ),0,0)
n_nan = np.cumsum(np.isnan(padded_x))
cumsum = np.nancumsum(padded_x) 
window_sum = cumsum[N+1:] - cumsum[:-(N+1)] - x # subtract value of interest from sum of all values within window
window_n_nan = n_nan[N+1:] - n_nan[:-(N+1)] - np.isnan(x)
window_n_values = (N - window_n_nan)
movavg = (window_sum) / (window_n_values)

Mã này chỉ hoạt động cho Ns. Nó có thể được điều chỉnh cho các số lẻ bằng cách thay đổi np.insert của padded_x và n_nan.

Ví dụ đầu ra (thô màu đen, Movavg màu xanh lam): dữ liệu thô (màu đen) và trung bình di chuyển (màu xanh) gồm 10 điểm xung quanh mỗi giá trị, không bao gồm giá trị đó.  giá trị nan được bỏ qua.

Mã này có thể được điều chỉnh dễ dàng để loại bỏ tất cả các giá trị trung bình di chuyển được tính toán từ ít hơn cutoff = 3 giá trị không nan.

window_n_values = (N - window_n_nan).astype(float) # dtype must be float to set some values to nan
cutoff = 3
window_n_values[window_n_values<cutoff] = np.nan
movavg = (window_sum) / (window_n_values)

dữ liệu thô (màu đen) và trung bình di chuyển (màu xanh) trong khi bỏ qua bất kỳ cửa sổ nào có ít hơn 3 giá trị không phải là nan


2

Chỉ sử dụng Thư viện chuẩn Python (Hiệu quả bộ nhớ)

Chỉ cần cung cấp một phiên bản khác của việc sử dụng thư viện tiêu chuẩn dequemà thôi. Tôi khá ngạc nhiên khi hầu hết các câu trả lời đang sử dụng pandashoặc numpy.

def moving_average(iterable, n=3):
    d = deque(maxlen=n)
    for i in iterable:
        d.append(i)
        if len(d) == n:
            yield sum(d)/n

r = moving_average([40, 30, 50, 46, 39, 44])
assert list(r) == [40.0, 42.0, 45.0, 43.0]

Trên thực tế tôi đã tìm thấy một triển khai khác trong tài liệu python

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

Tuy nhiên, việc thực hiện có vẻ phức tạp hơn một chút so với thực tế. Nhưng nó phải nằm trong tài liệu python tiêu chuẩn vì một lý do, ai đó có thể nhận xét về việc triển khai tài liệu của tôi và tài liệu chuẩn không?


2
Một điểm khác biệt lớn là bạn tiếp tục tổng hợp các thành viên cửa sổ mỗi lần lặp và họ cập nhật hiệu quả tổng (loại bỏ một thành viên và thêm một thành viên khác). về mức độ phức tạp, bạn đang thực hiện các O(n*d) phép tính ( dlà kích thước của cửa sổ, nkích thước của vòng lặp) và chúng đang thực hiệnO(n)
Iftah

@Iftah, tốt đẹp, cảm ơn vì lời giải thích, bạn đã đúng.
MaThMaX

2

Với các biến của @ Aikude, tôi đã viết một-liner.

import numpy as np

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3

mean = [np.mean(mylist[x:x+N]) for x in range(len(mylist)-N+1)]
print(mean)

>>> [2.0, 3.0, 4.0, 5.0, 6.0]

1

Mặc dù có giải pháp cho câu hỏi này ở đây, xin vui lòng xem giải pháp của tôi. Nó rất đơn giản và làm việc tốt.

import numpy as np
dataset = np.asarray([1, 2, 3, 4, 5, 6, 7])
ma = list()
window = 3
for t in range(0, len(dataset)):
    if t+window <= len(dataset):
        indices = range(t, t+window)
        ma.append(np.average(np.take(dataset, indices)))
else:
    ma = np.asarray(ma)

1

Từ việc đọc các câu trả lời khác, tôi không nghĩ đây là câu hỏi yêu cầu, nhưng tôi đã đến đây với nhu cầu giữ trung bình hoạt động của một danh sách các giá trị đang tăng kích thước.

Vì vậy, nếu bạn muốn giữ một danh sách các giá trị mà bạn đang có được từ một nơi nào đó (trang web, thiết bị đo, v.v.) và trung bình của các ngiá trị cuối cùng được cập nhật, bạn có thể sử dụng mã dưới đây, để giảm thiểu nỗ lực thêm mới các yếu tố:

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)

Và bạn có thể kiểm tra nó với, ví dụ:

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

Cung cấp cho:

Giá trị và giá trị trung bình của chúng là hàm của giá trị #


1

Một giải pháp khác chỉ cần sử dụng một thư viện tiêu chuẩn và deque:

from collections import deque
import itertools

def moving_average(iterable, n=3):
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable) 
    # create an iterable object from input argument
    d = deque(itertools.islice(it, n-1))  
    # create deque object by slicing iterable
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

# example on how to use it
for i in  moving_average([40, 30, 50, 46, 39, 44]):
    print(i)

# 40.0
# 42.0
# 45.0
# 43.0

1

Đối với mục đích giáo dục, hãy để tôi thêm hai giải pháp Numpy (chậm hơn giải pháp cumsum):

import numpy as np
from numpy.lib.stride_tricks import as_strided

def ra_strides(arr, window):
    ''' Running average using as_strided'''
    n = arr.shape[0] - window + 1
    arr_strided = as_strided(arr, shape=[n, window], strides=2*arr.strides)
    return arr_strided.mean(axis=1)

def ra_add(arr, window):
    ''' Running average using add.reduceat'''
    n = arr.shape[0] - window + 1
    indices = np.array([0, window]*n) + np.repeat(np.arange(n), 2)
    arr = np.append(arr, 0)
    return np.add.reduceat(arr, indices )[::2]/window

Các hàm được sử dụng: as_strided , add.reduceat


1

Tất cả các giải pháp nói trên đều kém vì chúng thiếu

  • tốc độ do một con trăn bản địa thay vì thực hiện vectơ numpy,
  • ổn định số do sử dụng kém numpy.cumsum, hoặc
  • tốc độ do O(len(x) * w)thực hiện như kết luận.

Được

import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000

Lưu ý rằng x_[:w].sum()bằng x[:w-1].sum(). Vì vậy, đối với mức trung bình đầu tiên numpy.cumsum(...)bổ sung x[w] / w(thông qua x_[w+1] / w), và trừ 0(từ x_[0] / w). Kết quả này trongx[0:w].mean()

Thông qua cumsum, bạn sẽ cập nhật mức trung bình thứ hai bằng cách cộng x[w+1] / wvà trừ x[0] / w, kết quả là x[1:w+1].mean().

Điều này diễn ra cho đến khi x[-w:].mean()đạt được.

x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w

Giải pháp này được vector hóa O(m), có thể đọc và ổn định về số lượng.


1

Làm thế nào về một bộ lọc trung bình di chuyển ? Nó cũng là một lớp lót và có một lợi thế, bạn có thể dễ dàng thao tác loại cửa sổ nếu bạn cần một cái gì đó khác ngoài hình chữ nhật, tức là. trung bình di chuyển đơn giản dài N của một mảng a:

lfilter(np.ones(N)/N, [1], a)[N:]

Và với cửa sổ hình tam giác được áp dụng:

lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]

Lưu ý: Tôi thường loại bỏ N mẫu đầu tiên là không có thật do đó [N:]ở cuối, nhưng nó không cần thiết và chỉ là vấn đề lựa chọn cá nhân.


-7

Nếu bạn chọn tự cuộn, thay vì sử dụng thư viện hiện có, vui lòng lưu ý đến lỗi dấu phẩy động và cố gắng giảm thiểu ảnh hưởng của nó:

class SumAccumulator:
    def __init__(self):
        self.values = [0]
        self.count = 0

    def add( self, val ):
        self.values.append( val )
        self.count = self.count + 1
        i = self.count
        while i & 0x01:
            i = i >> 1
            v0 = self.values.pop()
            v1 = self.values.pop()
            self.values.append( v0 + v1 )

    def get_total(self):
        return sum( reversed(self.values) )

    def get_size( self ):
        return self.count

Nếu tất cả các giá trị của bạn có cùng độ lớn, thì điều này sẽ giúp duy trì độ chính xác bằng cách luôn luôn thêm các giá trị có độ lớn gần tương tự nhau.


15
Đây là một câu trả lời không rõ ràng khủng khiếp, ít nhất là một số nhận xét trong mã hoặc giải thích lý do tại sao điều này giúp lỗi dấu phẩy động sẽ tốt.
Gabe

Trong câu cuối cùng của tôi, tôi đã cố gắng chỉ ra lý do tại sao nó giúp lỗi dấu phẩy động. Nếu hai giá trị xấp xỉ cùng một thứ tự cường độ, thì việc thêm chúng sẽ mất độ chính xác thấp hơn so với khi bạn thêm một số rất lớn vào một số rất nhỏ. Mã này kết hợp các giá trị "liền kề" theo cách mà ngay cả các khoản tiền trung gian phải luôn luôn có độ lớn hợp lý, để giảm thiểu lỗi dấu phẩy động. Không có gì là bằng chứng ngu ngốc nhưng phương pháp này đã cứu được một vài dự án được thực hiện rất kém trong sản xuất.
Mayur Patel

1. được áp dụng cho bài toán ban đầu, điều này sẽ rất chậm (trung bình tính toán), vì vậy điều này chỉ là không liên quan 2. để chịu sự cố về độ chính xác của các số 64 bit, người ta phải tổng hợp >> 2 ^ 30 gần như số bằng nhau.
Alleo

@Alleo: Thay vì thực hiện một bổ sung cho mỗi giá trị, bạn sẽ thực hiện hai bổ sung. Bằng chứng là giống như vấn đề lật bit. Tuy nhiên, điểm của câu trả lời này không nhất thiết là hiệu suất, mà là độ chính xác. Việc sử dụng bộ nhớ cho các giá trị trung bình 64 bit sẽ không vượt quá 64 thành phần trong bộ đệm, do đó, nó cũng thân thiện trong việc sử dụng bộ nhớ.
Mayur Patel

Đúng, bạn đúng khi điều này cần nhiều thao tác hơn gấp đôi so với tổng đơn giản, nhưng vấn đề ban đầu là tính toán trung bình , không chỉ là tổng. Điều này có thể được thực hiện trong O (n), nhưng câu trả lời của bạn yêu cầu O (mn), trong đó m là kích thước của cửa sổ.
Alleo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.