Làm thế nào để tính toán trung bình động bằng cách sử dụng NumPy?


109

Dường như không có chức năng nào chỉ đơn giản tính toán đường trung bình động trên numpy / scipy, dẫn đến các giải pháp phức tạp .

Câu hỏi của tôi có hai phần:

  • Cách dễ nhất để (một cách chính xác) thực hiện đường trung bình động với numpy là gì?
  • Vì điều này có vẻ không tầm thường và dễ xảy ra lỗi, có lý do chính đáng để không bao gồm pin trong trường hợp này?

19
Giải pháp tích chập dường như không phức tạp đối với tôi!
wim

4
Không phải trung bình động chỉ là một bộ lọc thông thấp (tức là 'mờ')? Đẹp, chắc chắn đó là chính xác những loại điều chập có nghĩa là cho ...
user541686

@mmgp Tôi đoán tôi đã hy vọng là sai, hoặc có một lý do rõ ràng.
goncalopp

3
@wim Nó chỉ có nghĩa là một cách chơi chữ. Nhưng thực tế là câu hỏi tồn tại có nghĩa là không đơn giản để tạo ra một đường trung bình động từ numpy.convolute.
goncalopp

Câu trả lời:


164

Nếu bạn chỉ muốn có một đơn giản phi trọng di chuyển trung bình, bạn có thể dễ dàng thực hiện nó với np.cumsum, mà có thể phương pháp nhanh hơn so với FFT dựa trên:

CHỈNH SỬA Đã sửa lỗi chỉ mục sai từng điểm một do Bean phát hiện trong mã. BIÊN TẬP

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

Vì vậy, tôi đoán câu trả lời là: nó thực sự dễ thực hiện và có thể numpy đã có một chút cồng kềnh với chức năng chuyên biệt.


10
Mã này không chính xác. ví dụ: moving_average ([1,2,5,10], n = 2) cho kết quả là [1, 3,5, 8,5]. Ngay cả trường hợp kiểm tra của người trả lời cho giá trị trung bình động từ 0 đến 19 là không chính xác, cho rằng giá trị trung bình của 0, 1 và 2 là 0,5. Làm thế nào mà nó nhận được 6 lượt ủng hộ?
JeremyKun

2
Cảm ơn bạn đã kiểm tra lỗi, hiện tại nó có vẻ đang hoạt động tốt. Đối với số phiếu ủng hộ, tôi đoán ý tưởng chung đằng sau câu trả lời được đặt nặng hơn là một lỗi riêng lẻ trong quá trình triển khai, nhưng ai biết được.
Jaime

2
Tôi đã tìm thấy các vấn đề. ret[n:] -= ret[:-n]không giống như ret[n:] = ret[n:] - ret[:-n]. Tôi đã sửa mã trong câu trả lời này. Chỉnh sửa: Không có ai đó chỉ cần đánh bại tôi với nó.
Timmmm

7
@Timmmm Tôi đã làm, đó thực sự là vấn đề. Nguyên tắc chung đằng sau câu trả lời này được sử dụng rộng rãi trong xử lý hình ảnh (bảng khu vực tổng hợp mà họ gọi là nó), vì vậy vấn đề phải được thực hiện. Một ví dụ điển hình về việc tối ưu hóa quá sớm, vì tôi nhớ lại thực hiện thao tác tại chỗ "bởi vì nó sẽ hiệu quả hơn." Trên mặt tươi sáng, nó có lẽ đã tạo ra câu trả lời sai nhanh hơn ...
Jaime

43
Hmmm, có vẻ như chức năng "dễ thực hiện" này thực sự khá dễ sai và đã thúc đẩy một cuộc thảo luận tốt về hiệu quả bộ nhớ. Tôi rất vui nếu có điều đó có nghĩa là biết rằng điều gì đó đã được thực hiện đúng.
Richard

81

Việc NumPy thiếu một chức năng dành riêng cho miền cụ thể có lẽ là do kỷ luật của Nhóm cốt lõi và sự trung thành với chỉ thị chính của NumPy: cung cấp kiểu mảng N-chiều , cũng như các hàm để tạo và lập chỉ mục các mảng đó. Giống như nhiều mục tiêu nền tảng khác, mục tiêu này không hề nhỏ và NumPy đã thực hiện nó một cách xuất sắc.

SciPy lớn hơn (nhiều) chứa một bộ sưu tập lớn hơn nhiều các thư viện dành riêng cho miền (được gọi là gói con bởi các nhà phát triển SciPy) - ví dụ: tối ưu hóa số ( tối ưu hóa ), xử lý tín hiệu ( signal ) và tích phân ( tích phân ).

Tôi đoán rằng chức năng bạn đang theo đuổi nằm trong ít nhất một trong các gói con SciPy ( có lẽ là scipy.signal ); tuy nhiên, tôi sẽ xem xét đầu tiên trong bộ sưu tập các scikits SciPy , xác định (các) scikit liên quan và tìm kiếm hàm quan tâm ở đó.

Scikits là các gói được phát triển độc lập dựa trên NumPy / SciPy và hướng đến một lĩnh vực kỹ thuật cụ thể (ví dụ: scikits-image , scikits-learning , v.v.) Một số gói trong số này (đặc biệt là OpenOpt tuyệt vời để tối ưu hóa số) được đánh giá cao, các dự án trưởng thành từ lâu trước khi chọn cư trú theo bảng xếp hạng scikits tương đối mới . Các Scikits Homepage thích vào danh sách trên khoảng 30 như scikits , mặc dù ít nhất là vài trong những không còn được phát triển tích cực.

Thực hiện theo lời khuyên này sẽ dẫn bạn đến scikits-timeeries ; tuy nhiên, gói đó không còn được phát triển tích cực nữa; Trên thực tế, Pandas đã trở thành AFAIK, thư viện chuỗi thời gian dựa trên thực tế NumPy .

Pandas có một số hàm có thể được sử dụng để tính toán đường trung bình động ; đơn giản nhất trong số này có lẽ là roll_mean , bạn sử dụng như vậy:

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

Bây giờ, chỉ cần gọi hàm roll_mean truyền trong đối tượng Series và kích thước cửa sổ , trong ví dụ dưới đây của tôi là 10 ngày .

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

xác minh rằng nó hoạt động - ví dụ: so sánh các giá trị 10 - 15 trong sê-ri ban đầu với sê-ri mới được làm mịn với giá trị trung bình cuộn

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

Hàm Roll_mean, cùng với khoảng một tá hàm khác được nhóm một cách chính thức trong tài liệu Pandas bên dưới các hàm cửa sổ di chuyển rubric ; thứ hai, nhóm hàm có liên quan trong Pandas được gọi là hàm có trọng số theo cấp số nhân (ví dụ: ewma , tính toán trung bình động có trọng số theo cấp số nhân). Thực tế là nhóm thứ hai này không được bao gồm trong nhóm đầu tiên (các chức năng cửa sổ di chuyển ) có lẽ là do các phép biến đổi có trọng số theo cấp số nhân không dựa vào cửa sổ có độ dài cố định


6
Gấu trúc có một loạt các chức năng cửa sổ chuyển động mạnh mẽ. Nhưng đối với tôi, nó có vẻ hơi quá nhiều so với một đường trung bình động đơn giản.
Jaime

6
Tôi nghi ngờ rằng việc tính toán đường trung bình động là một yêu cầu riêng biệt đối với OP hoặc đối với bất kỳ ai khác. Nếu bạn cần tính toán đường trung bình thì bạn gần như chắc chắn có một chuỗi thời gian, có nghĩa là bạn cần một cấu trúc dữ liệu cho phép bạn phù hợp với chỉ mục ngày-giờ cho dữ liệu của mình và đó là 'chi phí chung' mà bạn đề cập đến.
doug

2
Đầu tiên, cảm ơn bạn đã dành thời gian để viết câu trả lời rất bổ ích này. Thật vậy, tôi không thể thấy việc sử dụng đường trung bình động không liên quan đến chuỗi thời gian. Nhưng điều đó không có nghĩa là một nhu cầu để phù hợp với nó vào một datetime, hoặc thậm chí đến một khoảng thời gian lấy mẫu cụ thể (có thể là chưa biết)
goncalopp

3
Chỉ muốn nói thêm rằng hàm trung bình động đã được trích xuất vào thư viện Bottleneck nếu gấu trúc có vẻ quá nặng vì là một phụ thuộc.
robochat

4
'roll_mean' không phải là một phần của gấu trúc pf nữa, vui lòng xem trả lời bằng cách sử dụng '
roll

59

Một cách đơn giản để đạt được điều này là sử dụng np.convolve. Ý tưởng đằng sau điều này là tận dụng cách tính toán tích chập rời rạc và sử dụng nó để trả về giá trị trung bình cuộn . Điều này có thể được thực hiện bằng cách xoay vòng với một chuỗi np.onescó chiều dài bằng chiều dài cửa sổ trượt mà chúng ta muốn.

Để làm như vậy, chúng ta có thể xác định hàm sau:

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

Hàm này sẽ lấy tích chập của chuỗi xvà một chuỗi có độ dài w. Lưu ý rằng đã chọnmodevalidvì thế sản phẩm chập chỉ đưa ra cho điểm nơi trình tự chồng lên nhau hoàn toàn.


Vài ví dụ:

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

Đối với đường trung bình động có cửa sổ độ dài, 2chúng ta sẽ có:

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

Và đối với một cửa sổ có chiều dài 4:

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

Làm thế nào convolve làm việc?

Hãy có cái nhìn sâu hơn về cách tính toán tích chập rời rạc. Hàm sau đây nhằm mục đích sao chép cách np.convolvetính toán các giá trị đầu ra:

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

Điều nào, đối với cùng một ví dụ ở trên cũng sẽ mang lại:

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

Vì vậy, những gì đang được thực hiện ở mỗi bước là lấy sản phẩm bên trong giữa mảng cái và cửa sổ hiện tại . Trong trường hợp này, phép nhân với np.ones(w)là thừa vì chúng ta đang lấy trực tiếp sumdãy số.

Bellow là một ví dụ về cách các kết quả đầu ra đầu tiên được tính toán để nó rõ ràng hơn một chút. Giả sử chúng ta muốn có một cửa sổ 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

Và kết quả sau sẽ được tính là:

  [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

Và cứ thế, trả về giá trị trung bình động của chuỗi khi tất cả các phần chồng chéo đã được thực hiện.


Đây là một ý tưởng hay! Nó nhanh hơn câu trả lời của @ Jaime cho n nhỏ, nhưng trở nên chậm hơn cho n lớn hơn.
Felipe Gerard

Cảm ơn @FelipeGerard! Có như chỉ ra trong các ý kiến, trong khi phương pháp này có thể có lẽ không có hiệu quả như một số giải pháp NumPy khác, imo nó là tốt đẹp để có như một sự thay thế cho du khách trong tương lai cho sự đơn giản và súc tích của nó
yatu

Đôi khi, rất hữu ích khi mảng đầu ra có cùng kích thước với đầu vào. Đối với điều này mode='valid'có thể được thay thế bằng 'same'. Chỉ trong trường hợp này, các điểm cạnh sẽ hút về không.
Ilia Barahovski

15

Dưới đây là nhiều cách để thực hiện việc này, cùng với một số điểm chuẩn. Các phương pháp tốt nhất là các phiên bản sử dụng mã được tối ưu hóa từ các thư viện khác. Các bottleneck.move_meanphương pháp có lẽ là tốt nhất tất cả xung quanh. Cách scipy.convolvetiếp cận này cũng rất nhanh, có thể mở rộng và đơn giản về mặt cú pháp và khái niệm, nhưng không chia tỷ lệ tốt cho các giá trị cửa sổ rất lớn. Các numpy.cumsumphương pháp là tốt nếu bạn cần một tinh khiết numpycách tiếp cận.

Lưu ý: Một số trong số này (ví dụ bottleneck.move_mean) không được căn giữa và sẽ thay đổi dữ liệu của bạn.

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)

Thời gian, Cửa sổ nhỏ (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)

Thời gian, Cửa sổ lớn (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)

Bộ nhớ, Cửa sổ nhỏ (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

Bộ nhớ, Cửa sổ lớn (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

Câu trả lời sử dụng Pandas này được điều chỉnh từ phần trên, vì rolling_meankhông phải là một phần của Pandas nữa

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

Bây giờ, chỉ cần gọi hàm rollingtrên khung dữ liệu với kích thước cửa sổ, trong ví dụ dưới đây của tôi là 10 ngày.

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

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

Xem mẫu cơ bản bên dưới:

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)

Điều này cho thấy giá trị di chuyển dọc theo mỗi trục.

  • "mm" là nghĩa chuyển của "a".

  • "cửa sổ" là số mục nhập tối đa cần xem xét cho giá trị trung bình di chuyển.

  • "min_count" là số mục nhập tối thiểu để xem xét giá trị trung bình di chuyển (ví dụ: đối với phần tử đầu tiên hoặc nếu mảng có cá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ả.


2

Trong trường hợp bạn muốn quan tâm đến các điều kiện cạnh cẩn thận (chỉ tính giá trị trung bình từ các phần tử có sẵn ở các cạnh ), hàm sau sẽ thực hiện thủ thuật.

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

Hãy thử đoạn mã này. Tôi nghĩ nó đơn giản hơn và thực hiện công việc. lookback là cửa sổ của đường trung bình động.

Trong Data[i-lookback:i, 0].sum()tôi đã đặt 0để tham chiếu đến cột đầu tiên của tập dữ liệu nhưng bạn có thể đặt bất kỳ cột nào bạn thích trong trường hợp bạn có nhiều hơn một cột.


0

Tôi thực sự muốn có một hành vi hơi khác so với câu trả lời được chấp nhận. Tôi đang xây dựng một công cụ trích xuất tính năng trung bình động cho một sklearnđường ống, vì vậy tôi yêu cầu đầu ra của đường trung bình động phải có cùng thứ nguyên với đầu vào. Điều tôi muốn là để đường trung bình giả sử chuỗi không đổi, tức là đường trung bình động của [1,2,3,4,5]cửa sổ 2 sẽ cho[1.5,2.5,3.5,4.5,5.0] .

Đối với vectơ cột (trường hợp sử dụng của tôi), chúng tôi nhận được

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

Và đối với mảng

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

Tất nhiên, người ta không nhất thiết phải giả định các giá trị không đổi cho phần đệm, nhưng làm như vậy sẽ phù hợp trong hầu hết các trường hợp.


0

talib chứa một công cụ trung bình động đơn giản, cũng như các công cụ tính trung bình tương tự khác (tức là trung bình động hàm mũ). Dưới đây so sánh phương pháp này với một số giải pháp khác.


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

Một lưu ý là thực phải có các yếu tố của dtype = float. Nếu không, lỗi sau sẽ được nâng lên

Ngoại lệ: real không gấp đôi


0

Đây là một triển khai nhanh chóng bằng cách sử dụng numba (lưu ý các loại). Lưu ý rằng nó có chứa các nans nơi được dịch chuyển.

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

Điều này trả về nans lúc bắt đầu.
Adam Erickson

0

trung bình động

  • đảo ngược mảng tại i và chỉ cần lấy giá trị trung bình từ i đến n.

  • sử dụng khả năng hiểu danh sách để tạo mảng nhỏ một cách nhanh chóng.

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

Tôi sử dụng giải pháp của câu trả lời được chấp nhận , được sửa đổi một chút để có cùng độ dài cho đầu ra như đầu vào hoặc pandas'phiên bản như được đề cập trong nhận xét của một câu trả lời khác. Tôi tóm tắt cả hai ở đây với một ví dụ có thể tái tạo để tham khảo trong tương lai:

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

Bằng cách so sánh giải pháp bên dưới với giải pháp sử dụng cumsum of numpy, Giải pháp này mất gần một nửa thời gian . Điều này là do nó không cần phải đi qua toàn bộ mảng để thực hiện phép cộng và sau đó thực hiện tất cả các phép trừ. Hơn nữa, cumsum có thể " nguy hiểm " nếu mảng lớn và số lượng lớn ( có thể tràn ). Tất nhiên, cũng ở đây mối nguy hiểm tồn tại nhưng ít nhất chỉ được tổng hợp lại với nhau những con số thiết yếu.

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