ค่าเฉลี่ยเคลื่อนที่หรือค่าเฉลี่ยเคลื่อนที่


195

มีฟังก์ชั่น SciPy หรือฟังก์ชั่น NumPy หรือโมดูลสำหรับ Python ที่คำนวณค่าเฉลี่ยการรันของอาร์เรย์ 1D ที่ระบุหน้าต่างหรือไม่?

คำตอบ:


24

สำหรับวิธีแก้ปัญหาสั้น ๆ ที่รวดเร็วที่ทำงานได้ทั้งหมดในหนึ่งลูปโดยไม่ต้องพึ่งพาโค้ดด้านล่างใช้งานได้ดี

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)

46
ด่วน ?! การแก้ปัญหานี้คือลำดับความสำคัญช้ากว่าการแก้ปัญหาด้วย Numpy
บาร์ต

3
แม้ว่าวิธีการแก้ปัญหาแบบดั้งเดิมนี้จะเจ๋ง ๆ แต่ OP ก็ขอฟังก์ชั่น numpy / scipy - ซึ่งสันนิษฐานว่าจะเร็วกว่ามาก
มิส

255

UPD:การแก้ปัญหามีประสิทธิภาพมากขึ้นได้รับการเสนอโดยAlleoและjasaarim


คุณสามารถใช้np.convolveสำหรับ:

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

คำอธิบาย

หมายถึงการทำงานเป็นกรณีของการดำเนินการทางคณิตศาสตร์ของบิด สำหรับค่าเฉลี่ยที่รันคุณเลื่อนหน้าต่างไปตามอินพุตและคำนวณค่าเฉลี่ยของเนื้อหาของหน้าต่าง สำหรับสัญญาณ 1D แบบไม่ต่อเนื่องสังวัตนาก็เป็นสิ่งเดียวกันยกเว้นแทนที่จะเป็นค่าเฉลี่ยที่คุณคำนวณการรวมเชิงเส้นโดยพลการคือการคูณแต่ละองค์ประกอบด้วยสัมประสิทธิ์ที่สอดคล้องกันและเพิ่มผลลัพธ์ ค่าสัมประสิทธิ์ผู้หนึ่งสำหรับตำแหน่งในหน้าต่างแต่ละบางครั้งเรียกว่าบิดเคอร์เนล ตอนนี้ค่าเฉลี่ยเลขคณิตของค่า N คือ(x_1 + x_2 + ... + x_N) / Nดังนั้นเคอร์เนลที่สอดคล้องกันคือ(1/N, 1/N, ..., 1/N)และนั่นคือสิ่งที่เราได้รับโดยใช้np.ones((N,))/Nและนั่นคือสิ่งที่เราได้รับโดยใช้

ขอบ

modeข้อโต้แย้งของnp.convolveระบุวิธีการจัดการกับขอบ ฉันเลือกvalidโหมดที่นี่เพราะฉันคิดว่านั่นเป็นสิ่งที่คนส่วนใหญ่คาดหวังว่าการทำงานจะหมายถึงการทำงาน แต่คุณอาจมีลำดับความสำคัญอื่น ๆ นี่คือพล็อตที่แสดงให้เห็นถึงความแตกต่างระหว่างโหมด:

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

การรันโหมด convolve หมายถึง


5
ฉันชอบวิธีนี้เพราะมันสะอาด (หนึ่งบรรทัด) และค่อนข้างมีประสิทธิภาพ แต่การใช้ "วิธีแก้ปัญหาที่มีประสิทธิภาพ" ของ Alleo นั้นnumpy.cumsumมีความซับซ้อนมากกว่า
Ulrich Stern

2
@denfromufa ฉันเชื่อว่าเอกสารครอบคลุมการใช้งานที่ดีพอและยังเชื่อมโยงไปยัง Wikipedia ซึ่งอธิบายคณิตศาสตร์ เมื่อพิจารณาถึงจุดสำคัญของคำถามคุณคิดว่าคำตอบนี้จำเป็นต้องคัดลอกสิ่งเหล่านั้นหรือไม่?
ไพฑูรย์

@lapis การใช้งานของ convolve สำหรับค่าเฉลี่ยเคลื่อนที่ค่อนข้างผิดปกติและไม่ชัดเจน นี่คือคำอธิบายภาพที่ดีที่สุดที่ฉันได้พบ: matlabtricks.com/post-11/moving-average-by-convolution
denfromufa

สำหรับการพล็อตและงานที่เกี่ยวข้องจะเป็นประโยชน์ในการเติมด้วยค่า None คำแนะนำของฉัน (ไม่สวย แต่สั้น): `` `def moving_average (x, N, fill = True): return np.concatenate ([x สำหรับ x ใน [[None] * (N // 2 + N% 2) * fill, np.convolve (x, np.ones ((N,)) / N, mode = 'valid'), [None] * (N // 2) * fill,] ถ้า len (x)]) ` `` โค้ดดูน่าเกลียดมากในความคิดเห็นดังนั้น xD ฉันไม่ต้องการเพิ่มคำตอบอีกเนื่องจากมีจำนวนมาก แต่คุณอาจคัดลอกและวางลงใน IDE ของคุณ
Chaoste

146

ทางออกที่มีประสิทธิภาพ

Convolution นั้นดีกว่าวิธีที่ตรงไปตรงมามาก แต่ฉันเดาว่ามันใช้ FFT และค่อนข้างช้า อย่างไรก็ตามเป็นพิเศษสำหรับการคำนวณการวิ่งหมายถึงวิธีการดังต่อไปนี้ทำงานได้ดี

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

รหัสตรวจสอบ

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

ทราบว่าnumpy.allclose(result1, result2)เป็นTrueสองวิธีที่เทียบเท่ากัน ยิ่ง N ยิ่งมากความต่างของเวลาก็จะยิ่งมากขึ้น

คำเตือน: แม้ว่า cumsum จะเร็วขึ้น แต่จะมีข้อผิดพลาดของการเพิ่มจุดลอยตัวซึ่งอาจทำให้ผลลัพธ์ของคุณไม่ถูกต้อง / ไม่ถูกต้อง / ไม่สามารถยอมรับได้

ความคิดเห็นที่ชี้ให้เห็นปัญหาข้อผิดพลาดจุดลอยตัวที่นี่ แต่ฉันทำให้มันชัดเจนมากขึ้นในคำตอบ .

# 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)
  • ยิ่งคุณสะสมคะแนนมากกว่าข้อผิดพลาดจุดลอยตัวมากขึ้น (ดังนั้นคะแนน 1e5 จึงเป็นสิ่งที่สังเกตได้ยากคะแนน 1e6 มีความสำคัญมากกว่า 1e6 และคุณอาจต้องการรีเซ็ตตัวสะสม)
  • คุณสามารถโกงโดยใช้ np.longdoubleแต่ข้อผิดพลาดจุดลอยตัวของคุณจะยังคงมีความสำคัญสำหรับคะแนนจำนวนมาก (ประมาณ> 1e5 แต่ขึ้นอยู่กับข้อมูลของคุณ)
  • คุณสามารถพล็อตข้อผิดพลาดและดูว่ามันเพิ่มขึ้นค่อนข้างเร็ว
  • วิธีการแก้ปัญหาแบบ convolveนั้นช้าลง แต่ไม่มีการสูญเสียความแม่นยำของจุดลอยตัวนี้
  • โซลูชัน uniform_filter1dนั้นเร็วกว่าโซลูชัน cumsum และไม่มีการสูญเสียความแม่นยำของจุดลอยตัวนี้

3
ทางออกที่ดี! ลางสังหรณ์ของฉันnumpy.convolveคือ O (mn); ของเอกสารระบุว่าscipy.signal.fftconvolveการใช้ FFT
Ulrich Stern

3
วิธีนี้ไม่ได้จัดการกับขอบของอาร์เรย์ใช่หรือไม่
JoVe

6
วิธีแก้ปัญหาที่ดี แต่โปรดทราบว่าอาจประสบปัญหาข้อผิดพลาดเชิงตัวเลขสำหรับอาร์เรย์ขนาดใหญ่ตั้งแต่ท้ายอาร์เรย์คุณอาจลบตัวเลขขนาดใหญ่สองจำนวนเพื่อให้ได้ผลลัพธ์ที่มีขนาดเล็ก
Bas Swinckels

1
ส่วนนี้ใช้จำนวนเต็มแทนส่วนลอย: ช่วยให้running_mean([1,2,3], 2) array([1, 2])แทนที่xด้วย[float(value) for value in x]การหลอกลวง
ChrisW

4
เสถียรภาพเชิงตัวเลขของการแก้ปัญหานี้อาจกลายเป็นปัญหาหากxมีการลอยตัว ตัวอย่าง: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2ผลตอบแทนในขณะที่หนึ่งคาดว่า0.003125 0.0ข้อมูลเพิ่มเติม: en.wikipedia.org/wiki/Loss_of_significance
มิลาน

80

อัปเดต:ตัวอย่างด้านล่างแสดงpandas.rolling_meanฟังก์ชั่นเก่าที่ถูกลบไปในเวอร์ชันล่าสุดของแพนด้า เทียบเท่าทันสมัยของการเรียกใช้ฟังก์ชั่นด้านล่างจะเป็น

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

แพนด้าเหมาะสำหรับสิ่งนี้มากกว่า NumPy หรือ SciPy ฟังก์ชั่นมันrolling_meanทำงานได้สะดวก นอกจากนี้ยังส่งคืนอาร์เรย์ NumPy เมื่ออินพุตเป็นอาร์เรย์

เป็นการยากที่จะเอาชนะrolling_meanประสิทธิภาพด้วยการใช้ Python บริสุทธิ์ที่กำหนดเอง นี่คือตัวอย่างประสิทธิภาพการทำงานกับสองโซลูชันที่เสนอ:

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

นอกจากนี้ยังมีตัวเลือกที่ดีเกี่ยวกับวิธีจัดการกับค่าขอบ


6
Pandas rolling_mean เป็นเครื่องมือที่ดีสำหรับงาน แต่เลิกใช้แล้วสำหรับการจัดลำดับ ในอนาคตนุ่นปล่อยมันจะทำงานเฉพาะในซีรีส์นุ่น ตอนนี้เราจะเปิดใช้งานข้อมูลอาร์เรย์ที่ไม่ใช่นุ่นได้อย่างไร
Mike

5
@Mike rolling_mean () เลิกใช้แล้ว แต่ตอนนี้คุณสามารถใช้การหมุนและค่าเฉลี่ยแยกต่างหาก: df.rolling(windowsize).mean()ตอนนี้ทำงานแทน (เร็วมากฉันอาจเพิ่ม) สำหรับ 6,000 แถวชุด%timeit test1.rolling(20).mean()คืน1,000 ลูป, ดีที่สุดของ 3: 1.16 ms ต่อวน
Vlox

5
@Vlox df.rolling()ทำงานได้ดีพอปัญหาคือแม้รูปแบบนี้จะไม่สนับสนุน ndarrays ในอนาคต หากต้องการใช้เราจะต้องโหลดข้อมูลของเราลงใน Pandas Dataframe ก่อน ฉันชอบที่จะดูฟังก์ชั่นที่เพิ่มขึ้นนี้อย่างใดอย่างหนึ่งหรือnumpy scipy.signal
Mike

1
@ ไมค์เห็นด้วยอย่างยิ่ง ฉันกำลังดิ้นรนโดยเฉพาะอย่างยิ่งเพื่อให้ตรงกับ pandas .ewm (). mean () ความเร็วสำหรับอาร์เรย์ของตัวเอง (แทนที่จะต้องโหลดมันลงใน df ก่อน) ฉันหมายถึงมันยอดเยี่ยมมากที่มันเร็ว แต่ก็รู้สึกว่าการย้ายเข้าและออกจาก dataframes บ่อยเกินไป
Vlox

6
%timeit bottleneck.move_mean(x, N)เร็วกว่าวิธี cumsum และ pandas 3 ถึง 15 เท่าในพีซีของฉัน ลองดูที่มาตรฐานของพวกเขาใน repo ของที่README
mab

50

คุณสามารถคำนวณค่าเฉลี่ยการรันด้วย:

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

แต่มันช้า

โชคดีที่ numpy มีฟังก์ชั่นconvolveซึ่งเราสามารถใช้เพื่อเร่งความเร็วของสิ่งต่างๆ ค่าเฉลี่ยการทำงานเทียบเท่ากับการโน้มน้าวใจxกับเวกเตอร์ที่เป็นความยาวกับสมาชิกทุกคนเท่ากับN 1/Nการใช้งานที่ง่ายดายของ Convolve นั้นรวมถึงการเริ่มต้นชั่วคราวดังนั้นคุณต้องลบ N-1 แต้มแรก:

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

บนเครื่องของฉันรุ่นเร็วนั้นเร็วกว่า 20-30 เท่าขึ้นอยู่กับความยาวของเวกเตอร์อินพุตและขนาดของหน้าต่างเฉลี่ย

โปรดทราบว่าการสนทนาจะรวมถึง'same'โหมดที่ดูเหมือนว่าควรแก้ไขปัญหาการเริ่มต้นชั่วคราว แต่จะแยกระหว่างจุดเริ่มต้นและจุดสิ้นสุด


โปรดทราบว่าการลบจุด N-1 แรกยังคงมีผลต่อขอบเขตในจุดสุดท้าย เป็นวิธีที่ง่ายในการแก้ปัญหาคือการใช้mode='valid'ในconvolveซึ่งไม่จำเป็นต้องมีการโพสต์
ไพฑูรย์

1
@Psycho - mode='valid'ลบ transient จากปลายทั้งสองด้านใช่ไหม ถ้าlen(x)=10และN=4สำหรับค่าเฉลี่ยฉันจะต้องการผลลัพธ์ 10 รายการ แต่validส่งคืน 7
mtrw

1
มันลบชั่วคราวจากจุดสิ้นสุดและจุดเริ่มต้นไม่ได้มี ฉันเดาว่ามันเป็นเรื่องของลำดับความสำคัญฉันไม่ต้องการผลลัพธ์จำนวนเท่ากันในเรื่องค่าใช้จ่ายในการทำให้ความชันเป็นศูนย์ที่ไม่มีอยู่ในข้อมูล BTW นี่คือคำสั่งเพื่อแสดงความแตกต่างระหว่างโหมด: 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')(ที่มี pyplot และนำเข้า numpy)
ไพฑูรย์

runningMeanฉันมีผลข้างเคียงของการหาค่าเฉลี่ยด้วยเลขศูนย์เมื่อคุณออกจากอาร์เรย์ด้วยx[ctr:(ctr+N)]สำหรับด้านขวาของอาร์เรย์
mrgloom

runningMeanFastยังมีปัญหาผลกระทบชายแดนนี้
mrgloom

22

หรือโมดูลสำหรับไพ ธ อนที่คำนวณ

ในการทดสอบของฉันที่ Tradewave.net TA-lib จะชนะเสมอ:

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

ผล:

[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

ป้อนคำอธิบายรูปภาพที่นี่


NameError: name 'info' is not defined. ฉันได้รับข้อผิดพลาดนี้ครับ
Md. Rezwanul Haque

1
ดูเหมือนว่าเวลาของคุณจะเปลี่ยนไปหลังจากการปรับให้เรียบมันเป็นผลที่ต้องการหรือไม่?
mrgloom

@mrgloom ใช่เพื่อจุดประสงค์ในการสร้างภาพ; มิฉะนั้นจะปรากฏเป็นหนึ่งบรรทัดในแผนภูมิ Md. Rezwanul Haque คุณสามารถลบการอ้างอิงถึง PAIR และข้อมูลทั้งหมดได้ เหล่านั้นเป็นวิธีการ sandboxed ภายในสำหรับตอนนี้หมดอายุ tradewave.net
24419

21

สำหรับการแก้ปัญหาการใช้งานพร้อมดูhttps://scipy-cookbook.readthedocs.io/items/SignalSmooth.html มันให้ค่าเฉลี่ยการทำงานกับflatประเภทของหน้าต่าง โปรดทราบว่านี่เป็นวิธีที่ซับซ้อนกว่าวิธีง่าย ๆ แบบทำด้วยตัวเองเนื่องจากมันพยายามจัดการกับปัญหาในตอนเริ่มต้นและตอนท้ายของข้อมูลโดยสะท้อนให้เห็นถึง (ซึ่งอาจหรืออาจจะไม่ทำงานในกรณีของคุณ .. )

ในการเริ่มต้นคุณสามารถลอง:

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

1
วิธีนี้อาศัยnumpy.convolveความแตกต่างในการแก้ไขลำดับเท่านั้น
Alleo

10
ฉันมักจะรำคาญกับฟังก์ชั่นการประมวลผลสัญญาณที่ส่งคืนสัญญาณเอาต์พุตที่มีรูปร่างที่แตกต่างจากสัญญาณอินพุตเมื่อทั้งอินพุตและเอาต์พุตมีลักษณะเดียวกัน (เช่นสัญญาณชั่วคราว) มันแบ่งการติดต่อกับตัวแปรอิสระที่เกี่ยวข้อง (เช่นเวลาความถี่) ทำให้การวางแผนหรือการเปรียบเทียบไม่ใช่เรื่องโดยตรง ... อย่างไรก็ตามถ้าคุณแบ่งปันความรู้สึกคุณอาจต้องการเปลี่ยนบรรทัดสุดท้ายของฟังก์ชันที่เสนอเป็น y = np .convolve (w / w.sum (), s, โหมด = 'เดียว'); return y [window_len-1 :-( window_len-1)]
Christian O'Reilly

@ ChristianO'Reilly คุณควรโพสต์นั้นเป็นคำตอบที่แยกต่างหาก - นั่นคือสิ่งที่ฉันกำลังมองหาเพราะฉันมีอีกสองอาร์เรย์ที่ต้องตรงกับความยาวของข้อมูลที่ปรับให้เรียบเพื่อวางแผนและอื่น ๆ ที่ฉันอยากรู้ ว่าวิธีที่คุณทำอย่างนั้น - เป็นwขนาดของหน้าต่างและsข้อมูลหรือไม่
มิส

@Demis ดีใจที่มีความคิดเห็นช่วย ข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชั่น numpy convolve ที่นี่docs.scipy.org/doc/numpy-1.15.0/reference/generated/…ฟังก์ชั่น convolution ( en.wikipedia.org/wiki/Convolution ) โน้มน้าวสัญญาณสองสัญญาณด้วยกัน ในกรณีนี้มันทำให้สัญญาณของคุณสับสนด้วยหน้าต่างที่ถูกทำให้เป็นมาตรฐาน (เช่นพื้นที่รวม) (w / w.sum ())
Christian O'Reilly

21

คุณสามารถใช้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:

  • ให้เอาต์พุตที่มีรูปร่างเหมือนกัน (เช่นจำนวนคะแนน)
  • อนุญาตให้ใช้หลายวิธีในการจัดการเส้นขอบซึ่ง'reflect'เป็นค่าเริ่มต้น แต่ในกรณีของฉันฉันต้องการ'nearest'

นอกจากนี้ยังค่อนข้างรวดเร็ว (เร็วกว่าเกือบ 50 เท่าและเร็วกว่าnp.convolve2-5 เท่าเมื่อเทียบกับวิธีการแบบ cumsum ด้านบน ):

%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

นี่คือ 3 ฟังก์ชันที่ให้คุณเปรียบเทียบข้อผิดพลาด / ความเร็วของการใช้งานที่แตกต่างกัน:

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
นี่เป็นคำตอบเดียวที่ดูเหมือนจะคำนึงถึงปัญหาชายแดน (ค่อนข้างสำคัญโดยเฉพาะเมื่อวางแผน) ขอบคุณ!
Gabriel

1
ฉันประวัติuniform_filter1d, np.convolveสี่เหลี่ยมและตามมาด้วยnp.cumsum np.subtractผลลัพธ์ของฉัน: (1. ) convolve นั้นช้าที่สุด (2. ) cumsum / ลบประมาณ 20-30x เร็วขึ้น (3. ) uniform_filter1d เร็วกว่า cumsum / ลบประมาณ 2-3 เท่า ผู้ชนะคือ uniform_filter1d แน่นอน
เทรเวอร์บอยด์สมิ ธ

การใช้uniform_filter1dจะเร็วกว่าcumsumโซลูชัน (ประมาณ 2-5x) และuniform_filter1d ไม่ได้รับข้อผิดพลาดจุดลอยตัวขนาดใหญ่เช่นเดียวกับcumsumวิธีการแก้ปัญหา
Trevor Boyd Smith

15

ฉันรู้ว่านี่เป็นคำถามเก่า แต่นี่เป็นโซลูชันที่ไม่ใช้โครงสร้างข้อมูลหรือไลบรารีเพิ่มเติม มันเป็นเส้นตรงในจำนวนองค์ประกอบของรายการอินพุตและฉันไม่สามารถคิดวิธีอื่นเพื่อให้มีประสิทธิภาพมากขึ้น (จริง ๆ แล้วถ้าใครรู้วิธีที่ดีกว่าในการจัดสรรผลลัพธ์โปรดแจ้งให้เราทราบ)

หมายเหตุ:นี่จะเร็วกว่าการใช้อาร์เรย์ numpy แทนที่จะเป็นรายการ แต่ฉันต้องการกำจัดการพึ่งพาทั้งหมด นอกจากนี้ยังเป็นไปได้ที่จะปรับปรุงประสิทธิภาพโดยการดำเนินการแบบมัลติเธรด

ฟังก์ชั่นสันนิษฐานว่ารายการอินพุตเป็นหนึ่งมิติดังนั้นโปรดระวัง

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

ตัวอย่าง

สมมติว่าเรามีรายการdata = [ 1, 2, 3, 4, 5, 6 ]ที่เราต้องการคำนวณค่าเฉลี่ยการหมุนด้วยระยะเวลา 3 และคุณต้องการรายการผลลัพธ์ที่มีขนาดเท่ากันของอินพุตหนึ่ง (ซึ่งมักเป็นกรณี)

องค์ประกอบแรกมีดัชนี 0 ดังนั้นค่าเฉลี่ยการหมุนควรคำนวณในองค์ประกอบของดัชนี -2, -1 และ 0 เห็นได้ชัดว่าเราไม่มีข้อมูล [-2] และข้อมูล [-1] (เว้นแต่คุณต้องการใช้พิเศษ เงื่อนไขขอบเขต) ดังนั้นเราจึงสันนิษฐานว่าองค์ประกอบเหล่านั้นคือ 0 นี่เทียบเท่ากับ zero-padding รายการยกเว้นว่าเราไม่ได้ใส่มันจริงๆเพียงแค่ติดตามดัชนีที่ต้องใช้ padding (จาก 0 ถึง N-1)

ดังนั้นสำหรับองค์ประกอบแรกที่ N เราเพิ่งเพิ่มองค์ประกอบในการสะสม

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

จากองค์ประกอบ N + 1 ส่งต่อการสะสมอย่างง่ายไม่ทำงาน เราคาดว่าแต่เรื่องนี้จะแตกต่างจากresult[3] = (2 + 3 + 4)/3 = 3(sum + 4)/3 = 3.333

วิธีการคำนวณค่าที่ถูกต้องคือการลบdata[0] = 1จากจึงให้sum+4 sum + 4 - 1 = 9

นี้เกิดขึ้นเพราะขณะนี้sum = data[0] + data[1] + data[2]แต่มันยังเป็นจริงสำหรับทุกi >= Nเพราะก่อนที่จะลบที่เป็นsumdata[i-N] + ... + data[i-2] + data[i-1]


12

ฉันรู้สึกว่านี่สามารถแก้ไขได้อย่างหรูหราโดยใช้คอขวด

ดูตัวอย่างพื้นฐานด้านล่าง:

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" คือค่าเฉลี่ยเคลื่อนที่สำหรับ "a"

  • "window" คือจำนวนรายการสูงสุดที่ต้องพิจารณาสำหรับการย้ายค่าเฉลี่ย

  • "min_count" คือจำนวนรายการขั้นต่ำที่ต้องพิจารณาสำหรับการย้ายค่าเฉลี่ย (เช่นสำหรับองค์ประกอบสองสามตัวแรกหรือถ้าอาร์เรย์มีค่าน่าน)

ส่วนที่ดีคือคอขวดช่วยในการจัดการกับค่าน่านและยังมีประสิทธิภาพมาก


lib นี้เร็วจริงๆ ฟังก์ชั่นเฉลี่ยเคลื่อนที่ Python บริสุทธิ์ช้า Bootleneck เป็นไลบรารี PyData ซึ่งฉันคิดว่าเสถียรและสามารถรับการสนับสนุนอย่างต่อเนื่องจากชุมชน Python ดังนั้นทำไมไม่ใช้มัน
GoingMyWay

6

ฉันยังไม่ได้ตรวจสอบว่ามันเร็วแค่ไหน แต่คุณสามารถลอง:

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
นี่คือสิ่งที่ฉันจะทำ ทุกคนได้โปรดวิจารณ์ว่าทำไมมันเป็นวิธีที่ดีที่จะไป?
staggart

1
วิธีการแก้ปัญหาแบบหลามอย่างง่าย ๆ นี้ทำงานได้ดีสำหรับฉันโดยไม่ต้องใช้จำนวนมาก ฉันลงเอยด้วยการกลิ้งเป็นคลาสเพื่อใช้ซ้ำ
Matthew Tschiegg

6

คำตอบนี้มีวิธีแก้ปัญหาโดยใช้ไลบรารีมาตรฐานของ Python สำหรับสามสถานการณ์ที่แตกต่างกัน


วิ่งเฉลี่ยด้วย itertools.accumulate

นี่คือความทรงจำที่มีประสิทธิภาพหลาม 3.2+ แก้ปัญหาคอมพิวเตอร์ทำงานเฉลี่ยมากกว่า iterable itertools.accumulateของค่าโดยใช้ประโยชน์จาก

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

โปรดทราบว่าvaluesสามารถทำซ้ำได้ใด ๆ รวมถึงเครื่องกำเนิดไฟฟ้าหรือวัตถุอื่น ๆ ที่สร้างค่าได้ทันที

ก่อนอื่นอย่างเกียจคร้านสร้างผลรวมสะสมของค่า

>>> cumu_sum = accumulate(value_stream)

ถัดไปคือenumerateผลรวมสะสม (เริ่มต้นที่ 1) และสร้างตัวสร้างที่ให้ส่วนของค่าสะสมและดัชนีการแจงนับปัจจุบัน

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

คุณสามารถออกmeans = list(rolling_avg)หากคุณต้องการค่าทั้งหมดในหน่วยความจำในครั้งเดียวหรือโทรnextเพิ่มขึ้น
(แน่นอนคุณสามารถวนซ้ำrolling_avgด้วยการforวนซ้ำซึ่งจะเรียกnextโดยปริยาย)

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

วิธีการแก้ปัญหานี้สามารถเขียนเป็นฟังก์ชั่นดังต่อไปนี้

from itertools import accumulate

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

coroutineที่คุณสามารถส่งค่าในเวลาใดก็ได้

Coroutine นี้จะใช้ค่าที่คุณส่งและเก็บค่าเฉลี่ยของค่าที่เห็นอยู่

มันจะมีประโยชน์เมื่อคุณไม่มีค่า iterable แต่รับค่าที่จะเฉลี่ยหนึ่งโดยหนึ่งในช่วงเวลาที่แตกต่างกันตลอดชีวิตของโปรแกรมของคุณ

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
        

coroutine ทำงานดังนี้:

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

คำนวณค่าเฉลี่ยเหนือหน้าต่างเลื่อนขนาด N

ฟังก์ชันตัวสร้างนี้ใช้ตัววนซ้ำและขนาดของหน้าต่างN และให้ค่าเฉลี่ยมากกว่าค่าปัจจุบันภายในหน้าต่าง มันใช้dequeซึ่งเป็น datastructure คล้ายกับรายการ แต่ที่ดีที่สุดสำหรับการปรับเปลี่ยนอย่างรวดเร็ว ( pop, append) ที่ปลายทางทั้งสอง

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)
        

นี่คือฟังก์ชั่นการทำงาน:

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

สายไปงานเลี้ยงนิดหน่อย แต่ฉันได้สร้างฟังก์ชั่นเล็ก ๆ ของตัวเองที่ไม่พันรอบปลายหรือแผ่นรองด้วยศูนย์ที่ใช้แล้วเพื่อหาค่าเฉลี่ยเช่นกัน ในการรักษาเพิ่มเติมก็คือมันจะทำการสุ่มตัวอย่างสัญญาณอีกครั้งที่จุดเว้นระยะเชิงเส้น ปรับแต่งรหัสตามความประสงค์เพื่อรับคุณสมบัติอื่น ๆ

วิธีนี้เป็นการคูณเมทริกซ์อย่างง่าย ๆ ด้วยเคอร์เนลเกาส์เซียนแบบปกติ

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

การใช้งานอย่างง่าย ๆ ในสัญญาณไซน์พร้อมเสียงรบกวนแบบกระจายปกติ: ป้อนคำอธิบายรูปภาพที่นี่


สิ่งนี้ใช้ไม่ได้สำหรับฉัน (python 3.6) 1ไม่มีชื่อฟังก์ชั่นsumใช้np.sumแทน2ตัว@ดำเนินการ (ไม่รู้ว่ามันคืออะไร) ส่งข้อผิดพลาด ฉันอาจจะดูทีหลัง แต่ตอนนี้ฉันไม่มีเวลา
บาสเตียน

@เป็นผู้ประกอบการคูณเมทริกซ์ซึ่งดำเนินnp.matmul ตรวจสอบว่าy_inอาร์เรย์ของคุณเป็นอาร์เรย์ที่มีค่าหรือไม่นั่นอาจเป็นปัญหาหรือไม่
xyzzyqed

5

ฉันขอแนะนำให้แพนด้าทำสิ่งนี้เร็วกว่า:

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

วิธีนี้ใช้ค่าเฉลี่ยเคลื่อนที่ (MA) 3 ช่วงเวลาของคอลัมน์ "data" นอกจากนี้คุณยังสามารถคำนวณเวอร์ชันที่เลื่อนตัวอย่างเช่นเซลล์ที่ไม่รวมเซลล์ปัจจุบัน (เลื่อนด้านหลังหนึ่งรายการ) สามารถคำนวณได้อย่างง่ายดายดังนี้:

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

สิ่งนี้แตกต่างจากโซลูชันที่เสนอในปี 2559อย่างไร
Mr. T

2
เสนอทางแก้ปัญหาการใช้งานในปี 2016 ในขณะที่การใช้งานเหมืองpandas.rolling_mean pandas.DataFrame.rollingนอกจากนี้คุณยังสามารถคำนวณการเคลื่อนที่min(), max(), sum()เป็นต้นรวมถึงmean()วิธีนี้ได้อย่างง่ายดาย
Gursel Karacor

ในอดีตคุณต้องใช้วิธีการที่แตกต่างกันเช่นpandas.rolling_min, pandas.rolling_maxอื่น ๆ พวกเขาจะคล้ายกัน แต่แตกต่างกัน
Gursel Karacor

4

มีความคิดเห็นโดยmabฝังอยู่ในหนึ่งในคำตอบข้างต้นซึ่งมีวิธีนี้ bottleneckมีmove_meanค่าเฉลี่ยเคลื่อนที่อย่างง่าย:

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_countเป็นพารามิเตอร์ที่ใช้งานง่ายซึ่งโดยทั่วไปจะนำค่าเฉลี่ยเคลื่อนที่ไปยังจุดนั้นในอาร์เรย์ของคุณ หากคุณไม่ได้ตั้งค่าmin_countก็จะเท่ากับwindowและทุกอย่างขึ้นอยู่กับจุดที่จะเป็นwindownan


3

อีกวิธีหนึ่งในการหาค่าเฉลี่ยเคลื่อนที่โดยไม่ใช้ numpy, panda

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

จะพิมพ์ [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]


itertools.accumulate ไม่มีอยู่ใน python 2.7 แต่มีใน python 3.4
grayaii

3

ตอนนี้คำถามนี้เก่ากว่าตอนที่ NeXuS เขียนเมื่อเดือนที่แล้ว แต่ฉันชอบวิธีที่รหัสของเขาเกี่ยวกับคดีขอบ อย่างไรก็ตามเนื่องจากเป็น "ค่าเฉลี่ยเคลื่อนที่แบบง่าย" ผลลัพธ์จึงล่าช้าหลังข้อมูลที่ใช้ ผมคิดว่าการจัดการกับกรณีขอบในทางที่สร้างความพึงพอใจมากขึ้นกว่าโหมด NumPy ของvalid, sameและfullจะประสบความสำเร็จโดยการใช้วิธีการคล้ายกับconvolution()วิธีการตาม

การมีส่วนร่วมของฉันใช้ค่าเฉลี่ยส่วนกลางเพื่อจัดเรียงผลลัพธ์กับข้อมูลของพวกเขา เมื่อมีจุดที่น้อยเกินไปสำหรับหน้าต่างขนาดเต็มที่จะใช้ค่าเฉลี่ยการคำนวณจะคำนวณจากหน้าต่างเล็ก ๆ ที่ต่อเนื่องที่ขอบของอาร์เรย์ [ที่จริงแล้วจากหน้าต่างที่ใหญ่ขึ้นอย่างต่อเนื่อง แต่นั่นเป็นรายละเอียดการนำไปใช้งาน]

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

มันค่อนข้างช้าเพราะใช้convolve()และมีแนวโน้มว่าจะถูก spruce ขึ้นมาค่อนข้างมากโดย Pythonista จริงอย่างไรก็ตามฉันเชื่อว่าความคิดนั้นตั้งอยู่


3

มีคำตอบมากมายเกี่ยวกับการคำนวณหาค่าเฉลี่ย คำตอบของฉันเพิ่มคุณสมบัติพิเศษสองอย่าง:

  1. ละเว้นค่าน่าน
  2. คำนวณค่าเฉลี่ยสำหรับค่า N ที่อยู่ใกล้เคียงซึ่งไม่รวมถึงค่าของดอกเบี้ยเอง

คุณลักษณะที่สองนี้มีประโยชน์อย่างยิ่งสำหรับการพิจารณาว่าค่าใดแตกต่างจากแนวโน้มทั่วไปในจำนวนที่แน่นอน

ฉันใช้ numpy.cumsum เนื่องจากเป็นวิธีที่ประหยัดเวลามากที่สุด ( ดูคำตอบของ Alleo ด้านบน )

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)

รหัสนี้ใช้ได้กับ Ns เท่านั้น สามารถปรับเป็นตัวเลขคี่ได้โดยเปลี่ยน np.insert ของ padded_x และ n_nan

ตัวอย่างผลลัพธ์ (raw in black, movavg in blue): ข้อมูลดิบ (สีดำ) และค่าเฉลี่ยเคลื่อนที่ (สีน้ำเงิน) 10 คะแนนรอบแต่ละค่าไม่รวมค่านั้น  ค่านาโนจะถูกละเว้น

รหัสนี้สามารถปรับเปลี่ยนได้อย่างง่ายดายเพื่อลบค่าเฉลี่ยเคลื่อนที่ทั้งหมดที่คำนวณจากน้อยกว่า cutoff = 3 ค่าที่ไม่ใช่นาโน

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)

ข้อมูลดิบ (สีดำ) และค่าเฉลี่ยเคลื่อนที่ (สีน้ำเงิน) ในขณะที่ไม่สนใจหน้าต่างใด ๆ ที่มีค่าที่ไม่ใช่นาโนน้อยกว่า 3 ค่า


2

ใช้ไลบรารี Python มาตรฐานเท่านั้น (Memory Efficient)

เพียงให้รุ่นอื่นของการใช้ไลบรารีมาตรฐานdequeเท่านั้น มันค่อนข้างจะประหลาดใจให้กับผมว่าส่วนใหญ่ของคำตอบที่มีการใช้หรือpandasnumpy

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]

ที่จริงฉันพบการใช้งานอื่นในเอกสารหลาม

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

อย่างไรก็ตามการติดตั้งใช้งานดูเหมือนจะซับซ้อนกว่าที่ควรจะเป็น แต่มันจะต้องอยู่ในเอกสารมาตรฐานของงูหลามด้วยเหตุผลใครสามารถแสดงความคิดเห็นในการดำเนินการของฉันและเอกสารมาตรฐานได้หรือไม่?


2
ความแตกต่างที่สำคัญอย่างหนึ่งที่คุณยังคงรวมสมาชิกหน้าต่างแต่ละการวนซ้ำและพวกเขาปรับปรุงผลรวมได้อย่างมีประสิทธิภาพ (ลบสมาชิกหนึ่งรายและเพิ่มอีกหนึ่งราย) ในแง่ของความซับซ้อนที่คุณกำลังทำการ O(n*d) คำนวณ ( dเป็นขนาดของหน้าต่างnขนาดของการทำซ้ำได้) และพวกเขากำลังทำO(n)
Iftah

@Iftah ดีขอบคุณสำหรับคำอธิบายคุณพูดถูก
MaThMaX

2

ด้วยตัวแปรของ @ Aikude ฉันจึงเขียนหนึ่งซับ

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

แม้ว่าจะมีวิธีแก้ปัญหาสำหรับคำถามนี้ที่นี่โปรดดูที่โซลูชันของฉัน มันง่ายมากและทำงานได้ดี

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

จากการอ่านคำตอบอื่น ๆ ฉันไม่คิดว่านี่เป็นคำถามที่ถาม แต่ฉันมาที่นี่พร้อมความต้องการรักษาค่าเฉลี่ยที่กำลังทำงานอยู่ของรายการค่าที่เพิ่มขึ้นเรื่อย ๆ

ดังนั้นหากคุณต้องการเก็บรายการค่าที่คุณได้รับจากที่ใดที่หนึ่ง (ไซต์อุปกรณ์การวัด ฯลฯ ) และค่าเฉลี่ยของnค่าล่าสุดที่ได้รับการอัปเดตคุณสามารถใช้รหัสการร้องซึ่งช่วยลดความพยายามในการเพิ่มใหม่ องค์ประกอบ:

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)

และคุณสามารถทดสอบกับตัวอย่างเช่น:

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

ซึ่งจะช่วยให้:

ค่าและค่าเฉลี่ยเป็นฟังก์ชันของค่า #


1

โซลูชันอื่นเพียงแค่ใช้ไลบรารีมาตรฐานและ 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

เพื่อจุดประสงค์ด้านการศึกษาฉันขอเพิ่ม Numpy solution สองตัว (ซึ่งช้ากว่าโซลูชัน 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

ฟังก์ชั่นที่ใช้: as_strided , add.reduceat


1

วิธีแก้ปัญหาทั้งหมดที่กล่าวมานั้นแย่เพราะขาด

  • ความเร็วเนื่องจากไพ ธ อนดั้งเดิมแทนการใช้เวกเตอร์แบบ numpy
  • เสถียรภาพเชิงตัวเลขเนื่องจากการใช้numpy.cumsumหรือ
  • ความเร็วเนื่องจากการO(len(x) * w)ใช้งานเป็น convolutions

ป.ร. ให้ไว้

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

โปรดทราบว่าเท่ากับx_[:w].sum() x[:w-1].sum()ดังนั้นสำหรับค่าเฉลี่ยครั้งแรกการnumpy.cumsum(...)เพิ่มx[w] / w(ผ่านx_[w+1] / w) และการลบ0(จากx_[0] / w) ผลลัพธ์นี้ในx[0:w].mean()

คุณจะได้รับการอัปเดตค่าเฉลี่ยที่สองด้วยการเพิ่มx[w+1] / wและลบนอกจากนี้ยังx[0] / wส่งผลให้x[1:w+1].mean()ที่เกิดใน

สิ่งนี้จะดำเนินต่อไปจนกว่าx[-w:].mean()จะถึง

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

โซลูชันนี้เป็นแบบเวกเตอร์O(m)อ่านได้และมีความเสถียรเชิงตัวเลข


1

วิธีการเกี่ยวกับตัวกรองเฉลี่ยเคลื่อนที่ ? นอกจากนี้ยังเป็นหนึ่งซับและมีข้อได้เปรียบที่คุณสามารถจัดการประเภทของหน้าต่างได้อย่างง่ายดายหากคุณต้องการสิ่งอื่นนอกเหนือจากรูปสี่เหลี่ยมผืนผ้าเช่น ค่าเฉลี่ยเคลื่อนที่อย่างง่ายแบบยาว N ของ a:

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

และด้วยหน้าต่างสามเหลี่ยมที่ใช้:

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

หมายเหตุ: ฉันมักจะทิ้งตัวอย่าง N แรกเป็นปลอมดังนั้น[N:]ในตอนท้าย แต่ก็ไม่จำเป็นและเป็นเรื่องของทางเลือกส่วนตัวเท่านั้น


-7

หากคุณเลือกที่จะม้วนตัวเองแทนที่จะใช้ห้องสมุดที่มีอยู่โปรดระวังข้อผิดพลาดของจุดลอยตัวและพยายามลดผลกระทบของมัน:

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

หากค่าทั้งหมดของคุณมีลำดับความสำคัญเท่ากันดังนั้นสิ่งนี้จะช่วยรักษาความแม่นยำโดยการเพิ่มค่าของขนาดที่คล้ายกันโดยประมาณ


15
นี่เป็นคำตอบที่ไม่ชัดเจนมากอย่างน้อยความคิดเห็นในรหัสหรือคำอธิบายว่าทำไมสิ่งนี้ช่วยให้เกิดข้อผิดพลาดของจุดลอยตัวได้ดี
Gabe

ในประโยคสุดท้ายของฉันฉันพยายามที่จะระบุว่าทำไมมันช่วยให้เกิดข้อผิดพลาดจุดลอยตัว หากค่าสองค่านั้นมีขนาดใกล้เคียงกันดังนั้นการเพิ่มค่าเหล่านั้นจะสูญเสียความแม่นยำน้อยกว่าถ้าคุณเพิ่มค่าจำนวนมากลงในค่าที่มีขนาดเล็กมาก รหัสรวมค่า "ที่อยู่ติดกัน" ในลักษณะที่แม้แต่ผลรวมกลางควรอยู่ในระดับที่สมเหตุสมผลเสมอเพื่อลดข้อผิดพลาดของจุดลอยตัว ไม่มีอะไรพิสูจน์ได้ว่าเป็นเรื่องโง่ ๆ แต่วิธีนี้ช่วยให้โครงการที่ใช้งานในการผลิตมีคุณภาพต่ำ
Mayur Patel

1. ถูกนำไปใช้กับปัญหาดั้งเดิมนี่จะช้ามาก (ค่าเฉลี่ยในการคำนวณ) ดังนั้นมันจึงไม่เกี่ยวข้อง 2. เพื่อที่จะประสบปัญหาความแม่นยำของตัวเลข 64- บิตเราต้องสรุป >> 2 ^ 30 เกือบ ตัวเลขที่เท่ากัน
Alleo

@Alleo: แทนที่จะเพิ่มอีกหนึ่งค่าคุณจะทำสองอย่าง การพิสูจน์เหมือนกับปัญหาการพลิกบิต อย่างไรก็ตามประเด็นของคำตอบนี้ไม่จำเป็นต้องมีประสิทธิภาพ แต่มีความแม่นยำ การใช้หน่วยความจำสำหรับค่าเฉลี่ย 64 บิตจะไม่เกิน 64 องค์ประกอบในแคชดังนั้นจึงเป็นมิตรกับการใช้หน่วยความจำเช่นกัน
Mayur Patel

ใช่คุณพูดถูกว่าใช้เวลาดำเนินการมากกว่า 2x ผลรวมอย่างง่าย แต่ปัญหาดั้งเดิมคือการคำนวณค่าเฉลี่ยการทำงานไม่ใช่แค่ผลรวม ซึ่งสามารถทำได้ใน O (n) แต่คำตอบของคุณต้องการ O (mn) โดยที่ m คือขนาดของหน้าต่าง
Alleo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.