วิธีคำนวณค่าเฉลี่ยเคลื่อนที่โดยใช้ NumPy


111

ดูเหมือนว่าจะไม่มีฟังก์ชันใดที่เพียงแค่คำนวณค่าเฉลี่ยเคลื่อนที่บน numpy / scipy ซึ่งนำไปสู่ การแก้ปัญหาที่ซับซ้อน

คำถามของฉันมีสองเท่า:

  • วิธีใดที่ง่ายที่สุดในการใช้ค่าเฉลี่ยเคลื่อนที่ด้วย numpy (อย่างถูกต้อง)
  • เนื่องจากสิ่งนี้ดูเหมือนจะไม่สำคัญและเกิดข้อผิดพลาดมีเหตุผลที่ดีที่จะไม่รวมแบตเตอรี่ในกรณีนี้หรือไม่?

19
วิธีการแก้ปัญหาที่ซับซ้อนดูเหมือนจะไม่ซับซ้อนสำหรับฉัน!
Wim

4
ค่าเฉลี่ยเคลื่อนที่ไม่ใช่แค่ตัวกรองความถี่ต่ำ (เช่น 'เบลอ')? ค่อนข้างแน่ใจว่านั่นคือสิ่งที่ Convolution มีไว้สำหรับ ...
user541686

@mmgp ฉันคิดว่าฉันหวังว่าจะผิดหรือมีเหตุผลที่ดีและชัดเจน
goncalopp

3
@wim มันหมายถึงครึ่งหนึ่งของการเล่นสำนวน แต่ความจริงที่ว่าคำถามมีอยู่หมายความว่าการสร้างค่าเฉลี่ยเคลื่อนที่จาก numpy.convolute ไม่ตรงไปตรงมา
goncalopp

คำตอบ:


167

ถ้าคุณเพียงต้องการตรงไปตรงมาไม่ใช่ถ่วงน้ำหนักเฉลี่ยเคลื่อนที่คุณสามารถใช้งานได้อย่างง่ายดายด้วยnp.cumsumซึ่งอาจจะ เป็นวิธีการที่เร็วกว่า FFT ตาม:

แก้ไขแก้ไขการจัดทำดัชนีที่ไม่ถูกต้องทีละรายการที่พบโดย Bean ในโค้ด แก้ไข

def moving_average(a, n=3) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n - 1:] / n

>>> a = np.arange(20)
>>> moving_average(a)
array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.])
>>> moving_average(a, n=4)
array([  1.5,   2.5,   3.5,   4.5,   5.5,   6.5,   7.5,   8.5,   9.5,
        10.5,  11.5,  12.5,  13.5,  14.5,  15.5,  16.5,  17.5])

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


11
รหัสนี้ไม่ถูกต้อง เช่น moving_average ([1,2,5,10], n = 2) ให้ [1. , 3.5, 8.5] แม้แต่กรณีทดสอบของผู้ตอบสำหรับค่าเฉลี่ยเคลื่อนที่ของค่าตั้งแต่ 0 ถึง 19 ก็ไม่ถูกต้องโดยอ้างว่าค่าเฉลี่ย 0, 1 และ 2 คือ 0.5 ได้รับ 6 upvotes อย่างไร?
JeremyKun

2
ขอบคุณสำหรับการตรวจสอบข้อบกพร่องตอนนี้ดูเหมือนว่าจะทำงานได้ดี สำหรับการโหวตเพิ่มขึ้นฉันเดาว่าแนวคิดทั่วไปที่อยู่เบื้องหลังคำตอบนั้นมีน้ำหนักมากกว่าข้อผิดพลาดแบบ off-by-one ในการใช้งาน แต่ใครจะรู้
Jaime

3
ฉันพบปัญหาแล้ว คือไม่เหมือนกันret[n:] -= ret[:-n] ret[n:] = ret[n:] - ret[:-n]ฉันได้แก้ไขรหัสในคำตอบนี้แล้ว แก้ไข: ไม่มีใครเอาชนะฉันได้
Timmmm

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

44
อืมดูเหมือนว่าฟังก์ชั่น "ใช้งานง่าย" นี้ค่อนข้างง่ายที่จะทำผิดและได้สนับสนุนการอภิปรายที่ดีเกี่ยวกับประสิทธิภาพของหน่วยความจำ ฉันยินดีที่จะบวมถ้ามันหมายความว่ารู้ว่าสิ่งที่ทำถูกต้อง
Richard

81

การขาดฟังก์ชั่นเฉพาะโดเมนของ NumPy อาจเป็นเพราะความมีระเบียบวินัยของ Core Team และความซื่อสัตย์ต่อคำสั่งหลักของ NumPy: ระบุประเภทอาร์เรย์ N มิติตลอดจนฟังก์ชันสำหรับการสร้างและจัดทำดัชนีอาร์เรย์เหล่านั้น เช่นเดียวกับวัตถุประสงค์พื้นฐานหลายประการอันนี้ไม่เล็กและ NumPy ทำได้อย่างยอดเยี่ยม

SciPy ที่ใหญ่กว่า (มาก) มีคอลเล็กชันของไลบรารีเฉพาะโดเมนที่ใหญ่กว่ามาก (เรียกว่าแพ็กเกจย่อยโดย SciPy devs) - ตัวอย่างเช่นการเพิ่มประสิทธิภาพเชิงตัวเลข ( ปรับให้เหมาะสม ) การประมวลผลสัญญาณ ( สัญญาณ ) และแคลคูลัสเชิงปริพันธ์ ( รวม )

ฉันเดาว่าฟังก์ชั่นที่คุณใช้อยู่ในแพ็คเกจย่อยของ SciPy อย่างน้อยหนึ่งชุด ( อาจจะเป็นscipy.signal ) อย่างไรก็ตามฉันจะดูเป็นอันดับแรกในคอลเลกชันของ SciPy scikits เป็นอันดับแรกแรกระบุนักวิทย์ที่เกี่ยวข้องและมองหาฟังก์ชันที่น่าสนใจที่นั่น

Scikitsเป็นแพ็คเกจที่พัฒนาขึ้นโดยอิสระโดยใช้ NumPy / SciPy และนำไปสู่ระเบียบวินัยทางเทคนิคโดยเฉพาะ (เช่นscikits-image , scikits-learnเป็นต้น) หลายรายการเป็น (โดยเฉพาะอย่างยิ่งOpenOpt ที่ยอดเยี่ยมสำหรับการเพิ่มประสิทธิภาพเชิงตัวเลข) ได้รับการยกย่องอย่างสูง โครงการที่เติบโตเต็มที่ก่อนที่จะเลือกที่จะอยู่ภายใต้รูบริกscikits ที่ค่อนข้างใหม่ Scikitsโฮมเพจชอบรายการข้างต้นประมาณ 30 เช่นscikitsแต่อย่างน้อยหลายของผู้ที่ไม่ได้อยู่ภายใต้การพัฒนางาน

การปฏิบัติตามคำแนะนำนี้จะนำคุณไปสู่scikits-timeseries ; อย่างไรก็ตามแพคเกจนั้นไม่ได้อยู่ระหว่างการพัฒนาอีกต่อไป ผลก็คือนุ่นกลายเป็น AFAIK โดยพฤตินัย ไลบรารีอนุกรมเวลาตามหลักNumPy

นุ่นมีฟังก์ชั่นหลายอย่างที่สามารถนำมาใช้ในการคำนวณค่าเฉลี่ยเคลื่อนที่ ; วิธีที่ง่ายที่สุดน่าจะเป็นrolling_meanซึ่งคุณใช้ดังนี้:

>>> # the recommended syntax to import pandas
>>> import pandas as PD
>>> import numpy as NP

>>> # prepare some fake data:
>>> # the date-time indices:
>>> t = PD.date_range('1/1/2010', '12/31/2012', freq='D')

>>> # the data:
>>> x = NP.arange(0, t.shape[0])

>>> # combine the data & index into a Pandas 'Series' object
>>> D = PD.Series(x, t)

ตอนนี้เรียกใช้ฟังก์ชันrolling_mean ที่ส่งผ่านในวัตถุ Series และขนาดหน้าต่างซึ่งในตัวอย่างของฉันด้านล่างคือ10 วันวัน

>>> d_mva = PD.rolling_mean(D, 10)

>>> # d_mva is the same size as the original Series
>>> d_mva.shape
    (1096,)

>>> # though obviously the first w values are NaN where w is the window size
>>> d_mva[:3]
    2010-01-01         NaN
    2010-01-02         NaN
    2010-01-03         NaN

ตรวจสอบว่ามันใช้งานได้ - เช่นเปรียบเทียบค่า 10 - 15 ในซีรีส์เดิมเทียบกับซีรีส์ใหม่ที่เรียบด้วยค่าเฉลี่ยกลิ้ง

>>> D[10:15]
     2010-01-11    2.041076
     2010-01-12    2.041076
     2010-01-13    2.720585
     2010-01-14    2.720585
     2010-01-15    3.656987
     Freq: D

>>> d_mva[10:20]
      2010-01-11    3.131125
      2010-01-12    3.035232
      2010-01-13    2.923144
      2010-01-14    2.811055
      2010-01-15    2.785824
      Freq: D

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


6
หมีแพนด้ามีฟังก์ชันการเคลื่อนย้ายหน้าต่างที่หลากหลาย แต่สำหรับฉันแล้วดูเหมือนว่าค่าใช้จ่ายที่มากเกินไปสำหรับค่าเฉลี่ยเคลื่อนที่ธรรมดา
ไจ

6
ฉันสงสัยว่าการคำนวณค่าเฉลี่ยเคลื่อนที่เป็นข้อกำหนดที่แยกได้สำหรับ OP หรือสำหรับใครก็ได้ หากคุณต้องการคำนวณค่าเฉลี่ยเคลื่อนที่คุณแทบจะมีอนุกรมเวลาอยู่แล้วซึ่งหมายความว่าคุณต้องมีโครงสร้างข้อมูลที่ช่วยให้คุณสามารถจัดทำดัชนีวันที่และเวลากับข้อมูลของคุณได้และนั่นคือ 'ค่าใช้จ่าย' ที่คุณอ้างถึง
doug

2
ประการแรกขอขอบคุณที่สละเวลาเขียนคำตอบที่ให้ข้อมูลอย่างยิ่งนี้ อันที่จริงฉันไม่เห็นการใช้ค่าเฉลี่ยเคลื่อนที่ที่ไม่เกี่ยวข้องกับอนุกรมเวลา แต่นั่นไม่ได้หมายความว่าต้องสอดคล้องกับวันที่และเวลาหรือแม้แต่ช่วงเวลาการสุ่มตัวอย่างที่เฉพาะเจาะจง (อาจไม่เป็นที่รู้จัก)
goncalopp

3
แค่อยากจะเพิ่มว่าฟังก์ชันค่าเฉลี่ยเคลื่อนที่ได้ถูกแยกออกมาในไลบรารีBottleneckหากแพนด้าดูเหมือนว่ามีน้ำหนักมากเกินไปในการพึ่งพา
robochat

4
'Rolling_mean' ไม่ใช่แพนด้า pf อีกต่อไปโปรดดูการตอบกลับโดยใช้ 'rolling' แทน
Vladtn

66

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

ในการดำเนินการดังกล่าวเราสามารถกำหนดฟังก์ชันต่อไปนี้:

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'valid') / w

ฟังก์ชั่นนี้จะมีการบิดของลำดับและลำดับของคนที่มีความยาวของx wโปรดทราบว่าสิ่งที่เลือกmodeนั้นมีไว้validเพื่อให้ผลิตภัณฑ์ Convolution ได้รับเฉพาะจุดที่ลำดับทับซ้อนกันอย่างสมบูรณ์


ตัวอย่างบางส่วน:

x = np.array([5,3,8,10,2,1,5,1,0,2])

สำหรับค่าเฉลี่ยเคลื่อนที่ที่มีหน้าต่างความยาว2เราจะมี:

moving_average(x, 2)
# array([4. , 5.5, 9. , 6. , 1.5, 3. , 3. , 0.5, 1. ])

และสำหรับหน้าต่างความยาว4:

moving_average(x, 4)
# array([6.5 , 5.75, 5.25, 4.5 , 2.25, 1.75, 2.  ])

วิธีการconvolveทำงานหรือไม่

มาดูวิธีการคำนวณ Convolution แบบไม่ต่อเนื่องในเชิงลึกมากขึ้น ฟังก์ชันต่อไปนี้มีจุดมุ่งหมายเพื่อจำลองวิธีการnp.convolveคำนวณค่าเอาต์พุต:

def mov_avg(x, w):
    for m in range(len(x)-(w-1)):
        yield sum(np.ones(w) * x[m:m+w]) / w 

ซึ่งสำหรับตัวอย่างเดียวกันข้างต้นจะให้ผล:

list(mov_avg(x, 2))
# [4.0, 5.5, 9.0, 6.0, 1.5, 3.0, 3.0, 0.5, 1.0]

ดังนั้นสิ่งที่จะถูกดำเนินการในแต่ละขั้นตอนคือการใช้ผลิตภัณฑ์ภายในระหว่างแถวของคนและปัจจุบันหน้าต่าง ในกรณีนี้การคูณด้วยnp.ones(w)จะฟุ่มเฟือยเนื่องจากเรากำลังsumหาลำดับโดยตรง

Bellow เป็นตัวอย่างของการคำนวณผลลัพธ์แรกเพื่อให้ชัดเจนขึ้นเล็กน้อย สมมติว่าเราต้องการหน้าต่างw=4:

[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*5 + 1*3 + 1*8 + 1*10) / w = 6.5

และผลลัพธ์ต่อไปนี้จะคำนวณเป็น:

  [1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*3 + 1*8 + 1*10 + 1*2) / w = 5.75

ดังนั้นการส่งคืนค่าเฉลี่ยเคลื่อนที่ของลำดับเมื่อทำการทับซ้อนทั้งหมดแล้ว


1
นี่เป็นความคิดที่ดี! มันเร็วกว่าคำตอบของ @ ไจสำหรับ n ขนาดเล็ก แต่จะช้ากว่าสำหรับ n ขนาดใหญ่
Felipe Gerard

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

1
บางครั้งก็มีประโยชน์ที่จะมีอาร์เรย์เอาต์พุตที่มีขนาดเท่ากับอินพุต สำหรับสิ่งนี้mode='valid'สามารถแทนที่ด้วย'same'. ในกรณีนี้จุดขอบจะโน้มเข้าหาศูนย์
Ilia Barahovski

ในสถานการณ์ที่องค์ประกอบบางส่วนของอาร์เรย์ 'x' ของฟังก์ชันอาจเป็นไม่มีหรือศูนย์คุณจะได้ค่า 'x' ที่สอดคล้องกันของค่าที่ส่งคืนจากฟังก์ชันนี้ได้อย่างไร? ขนาดของอาร์เรย์ที่ส่งคืนจากฟังก์ชันนี้อาจมีขนาดเล็กกว่าอาร์เรย์ 'x' ที่ให้มา
Sun Bear

17

นี่คือวิธีต่างๆในการดำเนินการพร้อมกับเกณฑ์มาตรฐานบางส่วน วิธีที่ดีที่สุดคือเวอร์ชันที่ใช้โค้ดที่ปรับให้เหมาะสมจากไลบรารีอื่น bottleneck.move_meanวิธีการที่ดีที่สุดอาจเป็นรอบทั้งหมด scipy.convolveวิธีการยังมีความรวดเร็วขยายและไวยากรณ์และง่ายแนวคิด แต่ไม่ได้ดีขนาดสำหรับค่าหน้าต่างขนาดใหญ่มาก เป็นnumpy.cumsumวิธีที่ดีหากคุณต้องการความบริสุทธิ์numpyวิธี

หมายเหตุ:บางส่วน (เช่นbottleneck.move_mean) ไม่อยู่กึ่งกลางและจะเปลี่ยนข้อมูลของคุณ

import numpy as np
import scipy as sci
import scipy.signal as sig
import pandas as pd
import bottleneck as bn
import time as time

def rollavg_direct(a,n): 
    'Direct "for" loop'
    assert n%2==1
    b = a*0.0
    for i in range(len(a)) :
        b[i]=a[max(i-n//2,0):min(i+n//2+1,len(a))].mean()
    return b

def rollavg_comprehension(a,n):
    'List comprehension'
    assert n%2==1
    r,N = int(n/2),len(a)
    return np.array([a[max(i-r,0):min(i+r+1,N)].mean() for i in range(N)]) 

def rollavg_convolve(a,n):
    'scipy.convolve'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float')/n, 'same')[n//2:-n//2+1]  

def rollavg_convolve_edges(a,n):
    'scipy.convolve, edge handling'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float'), 'same')/sci.convolve(np.ones(len(a)),np.ones(n), 'same')  

def rollavg_cumsum(a,n):
    'numpy.cumsum'
    assert n%2==1
    cumsum_vec = np.cumsum(np.insert(a, 0, 0)) 
    return (cumsum_vec[n:] - cumsum_vec[:-n]) / n

def rollavg_cumsum_edges(a,n):
    'numpy.cumsum, edge handling'
    assert n%2==1
    N = len(a)
    cumsum_vec = np.cumsum(np.insert(np.pad(a,(n-1,n-1),'constant'), 0, 0)) 
    d = np.hstack((np.arange(n//2+1,n),np.ones(N-n)*n,np.arange(n,n//2,-1)))  
    return (cumsum_vec[n+n//2:-n//2+1] - cumsum_vec[n//2:-n-n//2]) / d

def rollavg_roll(a,n):
    'Numpy array rolling'
    assert n%2==1
    N = len(a)
    rolling_idx = np.mod((N-1)*np.arange(n)[:,None] + np.arange(N), N)
    return a[rolling_idx].mean(axis=0)[n-1:] 

def rollavg_roll_edges(a,n):
    # see /programming/42101082/fast-numpy-roll
    'Numpy array rolling, edge handling'
    assert n%2==1
    a = np.pad(a,(0,n-1-n//2), 'constant')*np.ones(n)[:,None]
    m = a.shape[1]
    idx = np.mod((m-1)*np.arange(n)[:,None] + np.arange(m), m) # Rolling index
    out = a[np.arange(-n//2,n//2)[:,None], idx]
    d = np.hstack((np.arange(1,n),np.ones(m-2*n+1+n//2)*n,np.arange(n,n//2,-1)))
    return (out.sum(axis=0)/d)[n//2:]

def rollavg_pandas(a,n):
    'Pandas rolling average'
    return pd.DataFrame(a).rolling(n, center=True, min_periods=1).mean().to_numpy()

def rollavg_bottlneck(a,n):
    'bottleneck.move_mean'
    return bn.move_mean(a, window=n, min_count=1)

N = 10**6
a = np.random.rand(N)
functions = [rollavg_direct, rollavg_comprehension, rollavg_convolve, 
        rollavg_convolve_edges, rollavg_cumsum, rollavg_cumsum_edges, 
        rollavg_pandas, rollavg_bottlneck, rollavg_roll, rollavg_roll_edges]

print('Small window (n=3)')
%load_ext memory_profiler
for f in functions : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[0:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,1001)

print('\nMemory\n')
print('Small window (n=3)')
N = 10**7
a = np.random.rand(N)
%load_ext memory_profiler
for f in functions[2:] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[2:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,1001)

ระยะเวลาหน้าต่างเล็ก (n = 3)

Direct "for" loop : 

4.14 s ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
3.96 s ± 27.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
1.07 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

scipy.convolve, edge handling : 
4.68 ms ± 9.69 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum : 
5.31 ms ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.52 ms ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.85 ms ± 9.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.3 ms ± 12.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Numpy array rolling : 
31.3 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy array rolling, edge handling : 
61.1 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

ระยะเวลาหน้าต่างบานใหญ่ (n = 1001)

Direct "for" loop : 
4.67 s ± 34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
4.46 s ± 14.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
103 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

scipy.convolve, edge handling : 
272 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

numpy.cumsum : 
5.19 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.7 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.67 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.31 ms ± 15.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

หน่วยความจำหน้าต่างขนาดเล็ก (n = 3)

The memory_profiler extension is already loaded. To reload it, use:
  %reload_ext memory_profiler

scipy.convolve : 
peak memory: 362.66 MiB, increment: 73.61 MiB

scipy.convolve, edge handling : 
peak memory: 510.24 MiB, increment: 221.19 MiB

numpy.cumsum : 
peak memory: 441.81 MiB, increment: 152.76 MiB

numpy.cumsum, edge handling : 
peak memory: 518.14 MiB, increment: 228.84 MiB

Pandas rolling average : 
peak memory: 449.34 MiB, increment: 160.02 MiB

bottleneck.move_mean : 
peak memory: 374.17 MiB, increment: 75.54 MiB

Numpy array rolling : 
peak memory: 661.29 MiB, increment: 362.65 MiB

Numpy array rolling, edge handling : 
peak memory: 1111.25 MiB, increment: 812.61 MiB

หน่วยความจำหน้าต่างขนาดใหญ่ (n = 1001)

scipy.convolve : 
peak memory: 370.62 MiB, increment: 71.83 MiB

scipy.convolve, edge handling : 
peak memory: 521.98 MiB, increment: 223.18 MiB

numpy.cumsum : 
peak memory: 451.32 MiB, increment: 152.52 MiB

numpy.cumsum, edge handling : 
peak memory: 527.51 MiB, increment: 228.71 MiB

Pandas rolling average : 
peak memory: 451.25 MiB, increment: 152.50 MiB

bottleneck.move_mean : 
peak memory: 374.64 MiB, increment: 75.85 MiB

11

คำตอบโดยใช้ Pandas นี้ดัดแปลงมาจากข้างบนเนื่องจากrolling_meanไม่ได้เป็นส่วนหนึ่งของ Pandas อีกต่อไป

# the recommended syntax to import pandas
import pandas as pd
import numpy as np

# prepare some fake data:
# the date-time indices:
t = pd.date_range('1/1/2010', '12/31/2012', freq='D')

# the data:
x = np.arange(0, t.shape[0])

# combine the data & index into a Pandas 'Series' object
D = pd.Series(x, t)

ตอนนี้เรียกใช้ฟังก์ชันrollingบนดาต้าเฟรมด้วยขนาดหน้าต่างซึ่งในตัวอย่างของฉันด้านล่างคือ 10 วัน

d_mva10 = D.rolling(10).mean()

# d_mva is the same size as the original Series
# though obviously the first w values are NaN where w is the window size
d_mva10[:11]

2010-01-01    NaN
2010-01-02    NaN
2010-01-03    NaN
2010-01-04    NaN
2010-01-05    NaN
2010-01-06    NaN
2010-01-07    NaN
2010-01-08    NaN
2010-01-09    NaN
2010-01-10    4.5
2010-01-11    5.5
Freq: D, dtype: float64

5

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

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

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=(5, 7))
mm = bn.move_mean(a, window=2, min_count=1)

สิ่งนี้ทำให้ค่าเฉลี่ยเคลื่อนที่ไปตามแต่ละแกน

  • "mm" คือค่าเฉลี่ยเคลื่อนที่ของ "a"

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

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

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


2

ในกรณีที่คุณต้องการดูแลสภาพขอบอย่างระมัดระวัง ( คำนวณค่าเฉลี่ยจากองค์ประกอบที่มีอยู่ที่ขอบเท่านั้น ) ฟังก์ชันต่อไปนี้จะทำเคล็ดลับ

import numpy as np

def running_mean(x, N):
    out = np.zeros_like(x, dtype=np.float64)
    dim_len = x.shape[0]
    for i in range(dim_len):
        if N%2 == 0:
            a, b = i - (N-1)//2, i + (N-1)//2 + 2
        else:
            a, b = i - (N-1)//2, i + (N-1)//2 + 1

        #cap indices to min and max indices
        a = max(0, a)
        b = min(dim_len, b)
        out[i] = np.mean(x[a:b])
    return out

>>> running_mean(np.array([1,2,3,4]), 2)
array([1.5, 2.5, 3.5, 4. ])

>>> running_mean(np.array([1,2,3,4]), 3)
array([1.5, 2. , 3. , 3.5])

1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

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

ในData[i-lookback:i, 0].sum()ฉันได้ระบุไว้0เพื่ออ้างถึงคอลัมน์แรกของชุดข้อมูล แต่คุณสามารถใส่คอลัมน์ใดก็ได้ที่คุณต้องการในกรณีที่คุณมีคอลัมน์มากกว่าหนึ่งคอลัมน์


0

จริงๆแล้วฉันต้องการพฤติกรรมที่แตกต่างจากคำตอบที่ยอมรับเล็กน้อย ฉันกำลังสร้างตัวแยกคุณสมบัติค่าเฉลี่ยเคลื่อนที่สำหรับsklearnไปป์ไลน์ดังนั้นฉันจึงต้องการให้เอาต์พุตของค่าเฉลี่ยเคลื่อนที่มีมิติเดียวกับอินพุต สิ่งที่ฉันต้องการคือให้ค่าเฉลี่ยเคลื่อนที่ถือว่าอนุกรมคงที่นั่นคือค่าเฉลี่ยเคลื่อนที่ของ[1,2,3,4,5]หน้าต่าง 2 จะให้[1.5,2.5,3.5,4.5,5.0]จะให้

สำหรับเวกเตอร์คอลัมน์ (กรณีการใช้งานของฉัน) เราได้รับ

def moving_average_col(X, n):
  z2 = np.cumsum(np.pad(X, ((n,0),(0,0)), 'constant', constant_values=0), axis=0)
  z1 = np.cumsum(np.pad(X, ((0,n),(0,0)), 'constant', constant_values=X[-1]), axis=0)
  return (z1-z2)[(n-1):-1]/n

และสำหรับอาร์เรย์

def moving_average_array(X, n):
  z2 = np.cumsum(np.pad(X, (n,0), 'constant', constant_values=0))
  z1 = np.cumsum(np.pad(X, (0,n), 'constant', constant_values=X[-1]))
  return (z1-z2)[(n-1):-1]/n

แน่นอนว่าเราไม่จำเป็นต้องถือว่าค่าคงที่สำหรับช่องว่างภายใน แต่การทำเช่นนั้นควรเพียงพอในกรณีส่วนใหญ่


0

ทาลิบประกอบด้วยเครื่องมือค่าเฉลี่ยเคลื่อนที่อย่างง่ายเช่นเดียวกับเครื่องมือหาค่าเฉลี่ยอื่น ๆ ที่คล้ายคลึงกัน (เช่นค่าเฉลี่ยเคลื่อนที่เอ็กซ์โปเนนเชียล) ด้านล่างนี้จะเปรียบเทียบวิธีการดังกล่าวกับโซลูชันอื่น ๆ


%timeit pd.Series(np.arange(100000)).rolling(3).mean()
2.53 ms ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit talib.SMA(real = np.arange(100000.), timeperiod = 3)
348 µs ± 3.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit moving_average(np.arange(100000))
638 µs ± 45.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

ข้อแม้อย่างหนึ่งคือของจริงต้องมีองค์ประกอบของdtype = float. มิฉะนั้นจะเกิดข้อผิดพลาดต่อไปนี้

ข้อยกเว้น: จริงไม่ใช่สองเท่า


0

นี่คือการนำไปใช้อย่างรวดเร็วโดยใช้ numba (คำนึงถึงประเภท) หมายเหตุมันมี nans ที่เลื่อน

import numpy as np
import numba as nb

@nb.jit(nb.float64[:](nb.float64[:],nb.int64),
        fastmath=True,nopython=True)
def moving_average( array, window ):    
    ret = np.cumsum(array)
    ret[window:] = ret[window:] - ret[:-window]
    ma = ret[window - 1:] / window
    n = np.empty(window-1); n.fill(np.nan)
    return np.concatenate((n.ravel(), ma.ravel())) 

สิ่งนี้ส่งคืน nans ที่จุดเริ่มต้น
Adam Erickson

0

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

  • ย้อนกลับอาร์เรย์ที่ i และใช้ค่าเฉลี่ยจาก i ถึง n

  • ใช้ความเข้าใจในรายการเพื่อสร้างอาร์เรย์ขนาดเล็กได้ทันที

x = np.random.randint(10, size=20)

def moving_average(arr, n):
    return [ (arr[:i+1][::-1][:n]).mean() for i, ele in enumerate(arr) ]
n = 5

moving_average(x, n)

0

ฉันใช้วิธีแก้ปัญหาของคำตอบที่ยอมรับซึ่งแก้ไขเล็กน้อยเพื่อให้มีความยาวเท่ากันสำหรับเอาต์พุตเป็นอินพุตหรือpandasเวอร์ชัน 'ตามที่กล่าวไว้ในความคิดเห็นของคำตอบอื่น ฉันสรุปทั้งสองที่นี่ด้วยตัวอย่างที่ทำซ้ำได้สำหรับการอ้างอิงในอนาคต:

import numpy as np
import pandas as pd

def moving_average(a, n):
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret / n

def moving_average_centered(a, n):
    return pd.Series(a).rolling(window=n, center=True).mean().to_numpy()

A = [0, 0, 1, 2, 4, 5, 4]
print(moving_average(A, 3))    
# [0.         0.         0.33333333 1.         2.33333333 3.66666667 4.33333333]
print(moving_average_centered(A, 3))
# [nan        0.33333333 1.         2.33333333 3.66666667 4.33333333 nan       ]

0

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

def moving_average(array_numbers, n):
    if n > len(array_numbers):
      return []
    temp_sum = sum(array_numbers[:n])
    averages = [temp_sum / float(n)]
    for first_index, item in enumerate(array_numbers[n:]):
        temp_sum += item - array_numbers[first_index]
        averages.append(temp_sum / float(n))
    return averages
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.