Jawaban:
Untuk solusi singkat dan cepat yang melakukan semuanya dalam satu lingkaran, tanpa dependensi, kode di bawah ini berfungsi dengan baik.
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)
UPD: solusi yang lebih efisien telah diusulkan oleh Alleo dan jasaarim .
Anda dapat menggunakannya np.convolve
untuk itu:
np.convolve(x, np.ones((N,))/N, mode='valid')
Mean berjalan adalah kasus operasi matematika konvolusi . Untuk mean yang sedang berjalan, Anda menggeser jendela di sepanjang input dan menghitung rata-rata dari isi jendela. Untuk sinyal 1D diskrit, konvolusi adalah hal yang sama, kecuali alih-alih berarti Anda menghitung kombinasi linear sewenang-wenang, yaitu mengalikan setiap elemen dengan koefisien yang sesuai dan menjumlahkan hasilnya. Koefisien-koefisien tersebut, satu untuk setiap posisi di jendela, kadang-kadang disebut kernel konvolusi . Sekarang, rata-rata aritmatika dari nilai-nilai N adalah (x_1 + x_2 + ... + x_N) / N
, jadi kernel yang sesuai adalah (1/N, 1/N, ..., 1/N)
, dan itulah yang kita dapatkan dengan menggunakan np.ones((N,))/N
.
The mode
argumen np.convolve
menspesifikasikan bagaimana menangani tepi. Saya memilih valid
mode di sini karena saya pikir itulah cara kebanyakan orang berharap menjalankan berarti bekerja, tetapi Anda mungkin memiliki prioritas lain. Berikut adalah plot yang menggambarkan perbedaan antara mode:
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()
numpy.cumsum
memiliki kompleksitas yang lebih baik.
Konvolusi jauh lebih baik daripada pendekatan langsung, tetapi (saya kira) menggunakan FFT dan karenanya sangat lambat. Namun khusus untuk komputasi yang sedang berjalan berarti pendekatan berikut berfungsi dengan baik
def running_mean(x, N):
cumsum = numpy.cumsum(numpy.insert(x, 0, 0))
return (cumsum[N:] - cumsum[:-N]) / float(N)
Kode untuk diperiksa
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
Perhatikan bahwa numpy.allclose(result1, result2)
adalah True
, dua metode yang setara. Semakin besar N, semakin besar pula perbedaan waktu.
# 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)
np.longdouble
tetapi kesalahan floating point Anda akan tetap signifikan untuk jumlah poin yang relatif besar (sekitar> 1e5 tetapi tergantung pada data Anda)numpy.convolve
adalah O (mn); yang docs menyebutkan bahwa scipy.signal.fftconvolve
penggunaan FFT.
running_mean([1,2,3], 2)
give array([1, 2])
. Mengganti x
dengan [float(value) for value in x]
melakukan trik.
x
mengandung pelampung. Contoh: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2
kembali 0.003125
sementara yang diharapkan 0.0
. Informasi lebih lanjut: en.wikipedia.org/wiki/Loss_of_significance
Pembaruan: Contoh di bawah ini menunjukkan pandas.rolling_mean
fungsi lama yang telah dihapus dalam versi panda terbaru. Setara modern dengan panggilan fungsi di bawah ini adalah
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])
panda lebih cocok untuk ini daripada NumPy atau SciPy. Fungsinya rolling_mean melakukan pekerjaan dengan nyaman. Itu juga mengembalikan array NumPy ketika input adalah sebuah array.
Sulit untuk mengalahkan rolling_mean
dalam kinerja dengan implementasi Python murni khusus. Berikut adalah contoh kinerja terhadap dua solusi yang diusulkan:
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
Ada juga opsi bagus untuk bagaimana menangani nilai-nilai tepi.
df.rolling(windowsize).mean()
sekarang berfungsi sebagai gantinya (sangat cepat saya dapat menambahkan). untuk seri 6.000 baris %timeit test1.rolling(20).mean()
menghasilkan 1000 loop, terbaik 3: 1,16 ms per loop
df.rolling()
berfungsi dengan cukup baik, masalahnya adalah bahkan formulir ini tidak akan mendukung ndarrays di masa mendatang. Untuk menggunakannya, kita harus memuat data kita ke dalam Bingkai Data Pandas terlebih dahulu. Saya ingin melihat fungsi ini ditambahkan ke salah satu numpy
atau scipy.signal
.
%timeit bottleneck.move_mean(x, N)
3 sampai 15 kali lebih cepat daripada metode cumsum dan panda di komputer saya. Lihatlah benchmark mereka di README repo .
Anda dapat menghitung rata-rata berjalan dengan:
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
Tapi lambat.
Untungnya, numpy menyertakan fungsi belok yang dapat kita gunakan untuk mempercepat. Mean berjalan setara dengan berbelit x
- belit dengan vektor yang N
panjang, dengan semua anggota sama dengan 1/N
. Implementasi convolve yang numpy termasuk transien awal, jadi Anda harus menghapus poin N-1 pertama:
def runningMeanFast(x, N):
return np.convolve(x, np.ones((N,))/N)[(N-1):]
Di komputer saya, versi cepat adalah 20-30 kali lebih cepat, tergantung pada panjang vektor input dan ukuran jendela rata-rata.
Perhatikan bahwa convolve memang menyertakan 'same'
mode yang sepertinya harus mengatasi masalah transien awal, tetapi membelahnya antara awal dan akhir.
mode='valid'
di convolve
mana tidak memerlukan post-processing.
mode='valid'
menghapus transien dari kedua ujungnya, kan? Jika len(x)=10
dan N=4
, untuk rata-rata berlari, saya ingin 10 hasil tetapi valid
mengembalikan 7.
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')
(dengan pyplot dan numpy diimpor).
runningMean
Apakah saya memiliki efek samping rata-rata dengan nol, ketika Anda keluar dari array dengan x[ctr:(ctr+N)]
untuk sisi kanan array.
runningMeanFast
juga memiliki masalah efek perbatasan ini.
atau modul untuk python yang menghitung
dalam pengujian saya di Tradewave.net TA-lib selalu menang:
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])
hasil:
[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
. Saya mendapatkan kesalahan ini, Pak.
Untuk solusi yang siap digunakan, lihat https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Ini memberikan rata-rata berjalan dengan flat
jenis jendela. Perhatikan bahwa ini sedikit lebih canggih daripada metode konvolusi do-it-yourself yang sederhana, karena metode ini mencoba menangani masalah di awal dan akhir data dengan merefleksikannya (yang mungkin atau mungkin tidak berfungsi dalam kasus Anda. ..)
Untuk memulainya, Anda dapat mencoba:
a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
numpy.convolve
, bedanya hanya dalam mengubah urutan.
w
ukuran jendela, dan s
data?
Anda dapat menggunakan 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'
default, tetapi dalam kasus saya, saya lebih suka'nearest'
Ini juga agak cepat (hampir 50 kali lebih cepat dari np.convolve
dan 2-5 kali lebih cepat daripada pendekatan cumsum yang diberikan di atas ):
%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
inilah 3 fungsi yang memungkinkan Anda membandingkan kesalahan / kecepatan implementasi yang berbeda:
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)]
uniform_filter1d
, np.convolve
dengan persegi panjang, dan np.cumsum
diikuti oleh np.subtract
. hasil saya: (1.) convolve adalah yang paling lambat. (2.) cumsum / kurangi sekitar 20-30x lebih cepat. (3.) uniform_filter1d sekitar 2-3x lebih cepat dari cumsum / kurangi. pemenang pasti uniform_filter1d.
uniform_filter1d
adalah lebih cepat dari cumsum
solusi (sekitar 2-5x). dan uniform_filter1d
tidak mendapatkan kesalahan floating point besar seperticumsum
solusinya.
Saya tahu ini adalah pertanyaan lama, tetapi di sini ada solusi yang tidak menggunakan struktur data atau pustaka tambahan. Itu linier dalam jumlah elemen daftar input dan saya tidak bisa memikirkan cara lain untuk membuatnya lebih efisien (sebenarnya jika ada yang tahu cara yang lebih baik untuk mengalokasikan hasilnya, tolong beri tahu saya).
CATATAN: ini akan jauh lebih cepat menggunakan array numpy daripada daftar, tapi saya ingin menghilangkan semua dependensi. Mungkin juga untuk meningkatkan kinerja dengan eksekusi multi-threaded
Fungsi ini mengasumsikan bahwa daftar input adalah satu dimensi, jadi berhati-hatilah.
### 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
Contoh
Asumsikan bahwa kita memiliki daftar data = [ 1, 2, 3, 4, 5, 6 ]
yang ingin kita hitung rata-rata bergulir dengan periode 3, dan bahwa Anda juga menginginkan daftar keluaran dengan ukuran yang sama dengan input (yang paling sering terjadi).
Elemen pertama memiliki indeks 0, sehingga rata-rata bergulir harus dihitung pada elemen indeks -2, -1 dan 0. Jelas kami tidak memiliki data [-2] dan data [-1] (kecuali jika Anda ingin menggunakan khusus syarat batas), jadi kami mengasumsikan bahwa elemen-elemen tersebut adalah 0. Ini setara dengan nol-padding daftar, kecuali kami tidak benar-benar pad itu, hanya melacak indeks yang membutuhkan padding (dari 0 hingga N-1).
Jadi, untuk elemen N pertama kami terus menambahkan elemen dalam akumulator.
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
Dari elemen N + 1 ke depan akumulasi sederhana tidak berfungsi. kami harapkan result[3] = (2 + 3 + 4)/3 = 3
tetapi ini berbeda dari (sum + 4)/3 = 3.333
.
Cara untuk menghitung nilai yang benar adalah untuk mengurangi data[0] = 1
dari sum+4
, sehingga memberikan sum + 4 - 1 = 9
.
Ini terjadi karena saat ini sum = data[0] + data[1] + data[2]
, tetapi itu juga berlaku untuk setiap i >= N
sebab, sebelum pengurangan, sum
adalah data[i-N] + ... + data[i-2] + data[i-1]
.
Saya merasa ini bisa diselesaikan dengan elegan menggunakan bottleneck
Lihat contoh dasar di bawah ini:
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" adalah rata-rata bergerak untuk "a".
"window" adalah jumlah maksimum entri yang perlu dipertimbangkan untuk moving average.
"min_count" adalah min jumlah entri yang perlu dipertimbangkan untuk memindahkan rata-rata (misalnya untuk beberapa elemen pertama atau jika array memiliki nilai nan).
Bagian baiknya adalah Bottleneck membantu menangani nilai-nilai nan dan juga sangat efisien.
Saya belum memeriksa seberapa cepat ini, tetapi Anda dapat mencoba:
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)
Jawaban ini berisi solusi menggunakan pustaka standar Python untuk tiga skenario yang berbeda.
itertools.accumulate
Ini adalah solusi Python 3.2+ yang efisien memori, menghitung rata-rata yang berjalan di atas nilai yang dapat diubah dengan memanfaatkannya itertools.accumulate
.
>>> from itertools import accumulate
>>> values = range(100)
Catatan yang values
bisa berupa iterable, termasuk generator atau objek lain yang menghasilkan nilai dengan cepat.
Pertama, malas membangun jumlah kumulatif dari nilai-nilai.
>>> cumu_sum = accumulate(value_stream)
Selanjutnya, enumerate
jumlah kumulatif (mulai dari 1) dan membangun generator yang menghasilkan sebagian kecil dari nilai akumulasi dan indeks enumerasi saat ini.
>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))
Anda dapat mengeluarkan means = list(rolling_avg)
jika Anda membutuhkan semua nilai dalam memori sekaligus atau menelepon next
secara bertahap.
(Tentu saja, Anda juga dapat beralih rolling_avg
dengan for
loop, yang akan memanggil next
secara implisit.)
>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0
Solusi ini dapat ditulis sebagai fungsi sebagai berikut.
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 ini mengkonsumsi nilai yang Anda kirim dan menjaga rata-rata berjalan dari nilai yang terlihat sejauh ini.
Ini berguna ketika Anda tidak memiliki iterable nilai tetapi meminta nilai untuk dirata-rata satu per satu pada waktu yang berbeda sepanjang hidup program Anda.
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 bekerja seperti ini:
>>> 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
Fungsi generator ini mengambil iterable dan ukuran jendela N
dan menghasilkan rata-rata di atas nilai saat ini di dalam jendela. Ini menggunakan deque
, yang merupakan struktur data yang mirip dengan daftar, tetapi dioptimalkan untuk modifikasi cepat ( pop
, append
) di kedua titik akhir .
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)
Berikut ini fungsinya:
>>> 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
Agak terlambat ke pesta, tapi saya sudah membuat fungsi kecil saya sendiri yang TIDAK membungkus ujung atau bantalan dengan nol yang kemudian digunakan untuk menemukan rata-rata juga. Sebagai perlakuan lebih lanjut adalah, bahwa itu juga sampel ulang sinyal pada titik-titik spasi linear. Kustomisasi kode sesuka hati untuk mendapatkan fitur lainnya.
Metode ini adalah perkalian matriks sederhana dengan kernel Gaussian yang dinormalisasi.
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
Penggunaan sederhana pada sinyal sinusoidal dengan tambahan noise terdistribusi normal:
sum
, menggunakan np.sum
bukannya 2 The @
operator (tidak tahu apa itu) melempar kesalahan. Saya mungkin memeriksanya nanti tetapi saya kekurangan waktu sekarang
Alih-alih numpy atau scipy, saya akan merekomendasikan panda untuk melakukan ini lebih cepat:
df['data'].rolling(3).mean()
Ini mengambil rata-rata bergerak (MA) dari 3 periode kolom "data". Anda juga dapat menghitung versi bergeser, misalnya yang mengecualikan sel saat ini (bergeser satu kembali) dapat dihitung dengan mudah sebagai:
df['data'].shift(periods=1).rolling(3).mean()
pandas.rolling_mean
saat menggunakan tambang pandas.DataFrame.rolling
. Anda juga dapat menghitung bergerak min(), max(), sum()
dll. Serta mean()
dengan metode ini dengan mudah.
pandas.rolling_min, pandas.rolling_max
dll. Mereka serupa namun berbeda.
Ada komentar oleh mab yang dimakamkan di salah satu jawaban di atas yang memiliki metode ini. bottleneck
memiliki move_mean
yang merupakan rata-rata bergerak sederhana:
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
adalah parameter praktis yang pada dasarnya akan membawa rata-rata bergerak ke titik itu dalam array Anda. Jika Anda tidak menyetel min_count
, itu akan sama window
, dan semuanya akan mencapai window
poin nan
.
Pendekatan lain untuk menemukan moving average tanpa menggunakan nanda, panda
import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a,
enumerate(itertools.accumulate(sample), 1)))
akan mencetak [2.0, 4.0, 6.0, 6.5, 7.4, 7.8333333333333]
Pertanyaan ini sekarang bahkan lebih tua daripada ketika NeXuS menulis tentang hal itu bulan lalu, TETAPI saya suka bagaimana kodenya berurusan dengan kasus tepi. Namun, karena ini adalah "rata-rata bergerak sederhana," hasilnya tertinggal di belakang data yang mereka terapkan. Saya berpikir bahwa berurusan dengan kasus tepi dengan cara yang lebih memuaskan daripada mode NumPy ini valid
, same
danfull
dapat dicapai dengan menerapkan pendekatan yang sama denganconvolution()
metode berbasis.
Kontribusi saya menggunakan rata-rata berjalan tengah untuk menyelaraskan hasilnya dengan data mereka. Ketika ada terlalu sedikit titik yang tersedia untuk jendela ukuran penuh untuk digunakan, rata-rata berjalan dihitung dari jendela yang lebih kecil berturut-turut di tepi array. [Sebenarnya, dari jendela yang lebih besar berturut-turut, tapi itu detail implementasi.]
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]])
Ini relatif lambat karena menggunakan convolve()
, dan mungkin bisa dirapikan cukup banyak oleh Pythonista yang sebenarnya, namun, saya percaya bahwa ide itu berlaku.
Ada banyak jawaban di atas tentang penghitungan rata-rata berjalan. Jawaban saya menambahkan dua fitur tambahan:
Fitur kedua ini sangat berguna untuk menentukan nilai mana yang berbeda dari tren umum dengan jumlah tertentu.
Saya menggunakan numpy.cumsum karena ini adalah metode yang paling efisien waktu ( lihat jawaban Alleo di atas ).
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)
Kode ini hanya berfungsi untuk Ns saja. Itu dapat disesuaikan untuk angka ganjil dengan mengubah np.insert dari padded_x dan n_nan.
Contoh output (mentah dalam warna hitam, movavg dengan warna biru):
Kode ini dapat dengan mudah diadaptasi untuk menghapus semua nilai rata-rata bergerak yang dihitung dari kurang dari cutoff = 3 nilai non-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)
Gunakan Hanya Pustaka Standar Python (Hemat Memori)
Berikan saja versi lain menggunakan perpustakaan standar deque
saja. Cukup mengejutkan bagi saya bahwa sebagian besar jawaban menggunakan pandas
atau 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]
Sebenarnya saya menemukan implementasi lain dalam python docs
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
Namun implementasinya menurut saya sedikit lebih kompleks dari yang seharusnya. Tetapi harus dalam dokumen python standar karena suatu alasan, dapatkah seseorang mengomentari implementasi tambang dan dokumen standar?
O(n*d)
perhitungan ( d
menjadi ukuran jendela, n
ukuran iterable) dan mereka melakukanO(n)
Meskipun ada solusi untuk pertanyaan ini di sini, silakan lihat solusi saya. Ini sangat sederhana dan bekerja dengan baik.
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)
Dari membaca jawaban-jawaban lain, saya kira ini bukan pertanyaan yang ditanyakan, tetapi saya tiba di sini dengan kebutuhan menjaga rata-rata daftar nilai yang terus bertambah.
Jadi, jika Anda ingin menyimpan daftar nilai yang Anda peroleh dari suatu tempat (situs, alat pengukur, dll.) Dan rata-rata dari nilai terakhir yang n
diperbarui, Anda dapat menggunakan kode di bawah ini, yang meminimalkan upaya penambahan baru elemen:
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)
Dan Anda dapat mengujinya dengan, misalnya:
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()
Pemberian yang mana:
Solusi lain hanya menggunakan perpustakaan dan deque standar:
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
Untuk tujuan pendidikan, izinkan saya menambahkan dua solusi Numpy (yang lebih lambat daripada solusi 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
Fungsi yang digunakan: as_strided , add.reduceat
Semua solusi tersebut buruk karena tidak ada
numpy.cumsum
, atauO(len(x) * w)
implementasi sebagai konvolusi.Diberikan
import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000
Perhatikan bahwa x_[:w].sum()
sama dengan x[:w-1].sum()
. Jadi untuk rata-rata pertama numpy.cumsum(...)
tambah x[w] / w
(via x_[w+1] / w
), dan kurangi 0
(dari x_[0] / w
). Ini menghasilkanx[0:w].mean()
Melalui cumsum, Anda akan memperbarui rata-rata kedua dengan menambah x[w+1] / w
dan mengurangi tambahan x[0] / w
, menghasilkan x[1:w+1].mean()
.
Ini berlangsung sampai x[-w:].mean()
tercapai.
x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w
Solusi ini adalah vektor O(m)
,, dapat dibaca dan stabil secara numerik.
Bagaimana dengan filter rata-rata bergerak ? Ini juga satu-liner dan memiliki keuntungan, bahwa Anda dapat dengan mudah memanipulasi jenis jendela jika Anda memerlukan sesuatu selain persegi panjang, yaitu. rata-rata bergerak N-long sederhana dari sebuah:
lfilter(np.ones(N)/N, [1], a)[N:]
Dan dengan jendela triangular diterapkan:
lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
Catatan: Saya biasanya membuang sampel N pertama sebagai palsu karena itu [N:]
pada akhirnya, tetapi itu tidak perlu dan masalah pilihan pribadi saja.
Jika Anda memang memilih untuk roll sendiri, daripada menggunakan perpustakaan yang ada, harap menyadari kesalahan floating point dan mencoba untuk meminimalkan efeknya:
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
Jika semua nilai Anda kira-kira sama dengan besarnya, maka ini akan membantu menjaga presisi dengan selalu menambahkan nilai dengan besaran yang hampir sama.