Jak obliczyć średnią ruchomą za pomocą NumPy?


110

Wydaje się, że nie ma funkcji, która po prostu oblicza średnią ruchomą na numpy / scipy, co prowadzi do zawiłych rozwiązań .

Moje pytanie jest dwojakie:

  • Jaki jest najłatwiejszy sposób (poprawnie) zaimplementowania średniej ruchomej za pomocą numpy?
  • Ponieważ wydaje się to nietrywialne i podatne na błędy, czy istnieje dobry powód, aby nie uwzględniać baterii w tym przypadku?

19
Rozwiązanie splotu nie wydaje mi się tak skomplikowane!
wim

4
Czy średnia ruchoma nie jest tylko filtrem dolnoprzepustowym (tj. „Rozmyciem”)?
Jestem prawie

@mmgp Myślę, że miałem nadzieję, że się mylę lub że był dobry, oczywisty powód.
goncalopp,

3
@wim To było w połowie pomyślane jako gra słów. Ale sam fakt, że pytanie istnieje, oznacza, że ​​nie jest łatwo utworzyć średnią ruchomą z numpy.convolute.
goncalopp,

Odpowiedzi:


165

Jeśli chcesz po prostu prosty non-ważona średnia ruchoma, można łatwo wdrożyć go np.cumsum, co może być to metody oparte szybciej niż FFT:

EDYCJA Poprawiono błędne indeksowanie, które Bean wykrył w kodzie. EDYTOWAĆ

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

Więc myślę, że odpowiedź brzmi: jest naprawdę łatwy do wdrożenia, a może numpy jest już trochę nadęty ze specjalistyczną funkcjonalnością.


10
Ten kod jest nieprawidłowy. np. move_average ([1,2,5,10], n = 2) daje [1., 3.5, 8.5]. Nawet przypadek testowy respondenta dla średniej ruchomej wartości od 0 do 19 jest niepoprawny, twierdząc, że średnia z 0, 1 i 2 wynosi 0,5. Jak zdobył 6 głosów pozytywnych?
JeremyKun

2
Dzięki za sprawdzenie błędów, teraz wygląda na to, że działa dobrze. Jeśli chodzi o głosy za pozytywnymi, to domyślam się, że ogólna idea stojąca za odpowiedzią została zważona bardziej niż pojedynczy błąd w implementacji, ale kto wie.
Jaime

2
Znalazłem problem. ret[n:] -= ret[:-n]NIE JEST TO SAMO jak ret[n:] = ret[n:] - ret[:-n]. Poprawiłem kod w tej odpowiedzi. Edycja: Nie, ktoś inny po prostu mnie pokonał.
Timmmm

7
@Timmmm Zrobiłem, to rzeczywiście był problem. Ogólna zasada stojąca za tą odpowiedzią jest szeroko stosowana w przetwarzaniu obrazu (nazywają to sumarycznymi tablicami powierzchni), więc problem musiał być w implementacji. Dobry przykład bycia nieco przedwczesną optymalizacją, ponieważ przypominam sobie wykonywanie tej operacji na miejscu, „ponieważ będzie bardziej wydajna”. Z drugiej strony, prawdopodobnie szybciej dało złą odpowiedź ...
Jaime,

43
Hmmm, wygląda na to, że ta „łatwa do zaimplementowania” funkcja jest w rzeczywistości dość łatwa do popełnienia błędu i sprzyjała dobrej dyskusji na temat wydajności pamięci. Cieszę się, że wzdęcie, jeśli oznacza to, że coś zostało zrobione dobrze.
Richard,

81

Brak w NumPy konkretnej funkcji specyficznej dla domeny jest prawdopodobnie spowodowany dyscypliną i wiernością Głównego Zespołu Podstawowej Dyrektywie NumPy: zapewnić N-wymiarowy typ tablicy , a także funkcje do tworzenia i indeksowania tych tablic. Podobnie jak wiele podstawowych celów, ten nie jest mały, a NumPy robi to znakomicie.

(O wiele) większy SciPy zawiera znacznie większy zbiór bibliotek specyficznych dla domeny (nazywanych przez twórców SciPy podpakietami ) - na przykład optymalizacja numeryczna ( optymalizacja ), przetwarzanie sygnału ( sygnał ) i rachunek całkowy ( integracja ).

Domyślam się, że funkcja, której szukasz, znajduje się w co najmniej jednym z podpakietów SciPy ( być może scipy.signal ); jednak najpierw zajrzałbym do kolekcji SciPy scikits , zidentyfikowałbym odpowiedni (e) scikit (y) i poszukał interesującej mnie funkcji.

Scikity to niezależnie opracowane pakiety oparte na NumPy / SciPy i skierowane do określonej dyscypliny technicznej (np. Scikits-image , scikits-learn itp.) Kilka z nich (w szczególności niesamowity OpenOpt do optymalizacji numerycznej) zostało wysoko ocenionych, dojrzałe projekty na długo przed podjęciem decyzji o zamieszkaniu w stosunkowo nowej rubryce scikits . Strona główna Scikits powyżej zawiera około 30 takich scikitów , chociaż przynajmniej kilka z nich nie jest już w fazie aktywnego rozwoju.

Postępowanie zgodnie z tą radą doprowadziłoby cię do serii scikits ; jednak pakiet ten nie jest już aktywnie rozwijany; W efekcie Pandy stały się, AFAIK, de facto biblioteką szeregów czasowych opartą na NumPy .

Pandy ma kilka funkcji, których można użyć do obliczenia średniej ruchomej ; najprostszym z nich jest prawdopodobnie rolling_mean , którego używasz w ten sposób:

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

Teraz wystarczy wywołać funkcję rolling_mean przekazującą obiekt Series i rozmiar okna , który w moim przykładzie poniżej wynosi 10 dni .

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

sprawdzić, czy zadziałało - np. porównać wartości 10-15 w oryginalnej serii z nową serią wygładzoną za pomocą średniej kroczącej

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

Funkcja rolling_mean wraz z kilkunastoma innymi funkcjami jest nieformalnie zgrupowana w dokumentacji Pandy pod rubryką „ Funkcje ruchomego okna” ; druga, powiązana grupa funkcji w Pandas jest określana jako funkcje ważone wykładniczo (np. ewma , która oblicza wykładniczo ruchomą średnią ważoną). Fakt, że ta druga grupa nie jest zawarta w pierwszej ( funkcje ruchomego okna ) jest prawdopodobnie spowodowana tym, że transformacje ważone wykładniczo nie opierają się na oknie o stałej długości


6
Pandy mają silny zestaw funkcji ruchomego okna. Ale wydaje mi się, że to trochę za dużo narzutów jak na zwykłą średnią ruchomą.
Jaime,

6
cóż, wątpię, czy obliczanie średniej ruchomej jest odosobnionym wymogiem dla PO lub prawie każdego innego. Jeśli potrzebujesz obliczyć średnią ruchomą, prawie na pewno masz szereg czasowy, co oznacza, że ​​potrzebujesz struktury danych, która pozwoli ci dostosować indeks daty i czasu do twoich danych i to jest `` narzut '', do którego się odnosisz.
Doug

2
Po pierwsze, dziękuję za poświęcenie czasu na napisanie tej niezwykle pouczającej odpowiedzi. Rzeczywiście, nie widzę zastosowania średniej ruchomej, która nie obejmuje szeregów czasowych. Ale to nie znaczy, że trzeba dostosować go do
daty i godziny

3
Chciałem tylko dodać, że funkcja średniej ruchomej została wyodrębniona do biblioteki Wąskie gardło, jeśli pandy wydają się zbyt ciężkie jako zależność.
robochat

4
„Rolling_mean” nie jest już częścią pf pandas, zobacz odpowiedź za pomocą „rolling”
Vladtn

63

Prostym sposobem na osiągnięcie tego jest użycie np.convolve. Ideą tego jest wykorzystanie sposobu obliczania splotu dyskretnego i wykorzystanie go do zwrócenia średniej kroczącej . Można to zrobić przez splatanie z sekwencją o np.onesdługości równej długości przesuwanego okna, jakiej chcemy.

W tym celu możemy zdefiniować następującą funkcję:

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

Ta funkcja będzie pobierać splot ciągu xi sekwencję długości w. Zwróć uwagę, że wybrana modejest validtaka, że ​​iloczyn splotu jest podawany tylko dla punktów, w których sekwencje całkowicie się pokrywają.


Kilka przykładów:

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

Dla średniej ruchomej z oknem długości 2mielibyśmy:

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

A dla okna o długości 4:

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

Jak to convolvedziała?

Przyjrzyjmy się dokładniej sposobowi obliczania splotu dyskretnego. Następująca funkcja ma na celu odtworzenie sposobu np.convolveobliczania wartości wyjściowych:

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

Co, dla tego samego przykładu powyżej, również dałoby:

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

Więc to, co jest robione na każdym kroku, to wzięcie iloczynu wewnętrznego między tablicą jedynek a bieżącym oknem . W tym przypadku mnożenie przez np.ones(w)jest zbędne, biorąc pod uwagę, że bezpośrednio bierzemy sumciąg.

Poniżej znajduje się przykład tego, jak obliczane są pierwsze wyniki, aby było trochę jaśniej. Załóżmy, że chcemy mieć okno 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

A następujący wynik zostałby obliczony jako:

  [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

I tak dalej, zwracając średnią ruchomą sekwencji po wykonaniu wszystkich nałożeń.


To niezły pomysł! Jest szybsza niż odpowiedź @ Jaime dla małego n, ale staje się wolniejsza dla większego n.
Felipe Gerard

Dzięki @FelipeGerard! Tak, jak wskazano w komentarzach, chociaż to podejście może nie być tak wydajne, jak niektóre inne odrętwienie, miło jest mieć alternatywę dla przyszłych odwiedzających, biorąc pod uwagę jego prostotę i zwięzłość
yatu

Czasami warto mieć tablicę wyjściową o takim samym rozmiarze jak dane wejściowe. W tym celu mode='valid'można zastąpić 'same'. Właśnie w tym przypadku punkty krawędziowe będą grawitować w kierunku zera.
Ilia Barahovski

W sytuacji, gdy niektóre elementy tablicy „x” funkcji mogą mieć wartość Brak lub zero, jak uzyskać odpowiednie wartości „x” zwróconych wartości z tej funkcji? Rozmiar tablicy zwróconej przez tę funkcję może być mniejszy niż dostarczona do niej tablica „x”.
Niedźwiedź Słońca

15

Oto kilka sposobów, aby to zrobić, wraz z niektórymi wzorcami. Najlepsze metody to wersje korzystające ze zoptymalizowanego kodu z innych bibliotek. bottleneck.move_meanMetoda jest prawdopodobnie najlepiej dookoła. scipy.convolvePodejście jest również bardzo szybki, rozszerzalny i składniowo i koncepcyjnie proste, ale nie jest dobrze skalować dla bardzo dużych wartości okiennych. numpy.cumsumMetoda jest dobra, jeśli potrzebujemy czystego numpypodejścia.

Uwaga: niektóre z nich (np. bottleneck.move_mean) Nie są wyśrodkowane i spowodują przesunięcie danych.

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)

Czas, małe okno (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)

Rozrząd, duże okno (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)

Pamięć, małe okno (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

Pamięć, duże okno (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

Ta odpowiedź za pomocą Pand jest dostosowana z góry, ponieważ rolling_meannie jest już częścią Pand

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

Teraz po prostu wywołaj funkcję rollingw ramce danych z rozmiarem okna, który w moim przykładzie poniżej wynosi 10 dni.

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

Czuję, że można to łatwo rozwiązać za pomocą wąskiego gardła

Zobacz podstawowy przykład poniżej:

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)

Daje to średnią ruchu wzdłuż każdej osi.

  • „mm” jest ruchomą średnią dla „a”.

  • „okno” to maksymalna liczba wpisów do rozważenia w przypadku średniej ruchomej.

  • „min_count” to minimalna liczba wpisów do rozważenia przy ruchomej średniej (np. dla pierwszego elementu lub jeśli tablica ma wartości nan).

Zaletą jest to, że wąskie gardło pomaga radzić sobie z wartościami nan, a także jest bardzo wydajne.


2

Jeśli chcesz uważnie zadbać o warunki brzegowe ( oblicz średnią tylko z dostępnych elementów na krawędziach ), poniższa funkcja załatwi sprawę .

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

Wypróbuj ten fragment kodu. Myślę, że to prostsze i spełnia swoje zadanie. okres ważności to okno średniej ruchomej.

W sekcji Data[i-lookback:i, 0].sum()umieściłem 0odniesienie do pierwszej kolumny zbioru danych, ale możesz umieścić dowolną kolumnę, jeśli masz więcej niż jedną kolumnę.


0

Właściwie chciałem trochę innego zachowania niż zaakceptowana odpowiedź. Budowałem ekstraktor funkcji średniej ruchomej dla sklearnpotoku, więc wymagałem, aby dane wyjściowe średniej ruchomej miały taki sam wymiar jak dane wejściowe. Chcę, aby średnia ruchoma zakładała, że ​​szereg pozostaje stały, tj. Dałby średnią ruchomą [1,2,3,4,5]z okna 2 [1.5,2.5,3.5,4.5,5.0].

W przypadku wektorów kolumnowych (mój przypadek użycia) otrzymujemy

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

I dla tablic

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

Oczywiście nie trzeba przyjmować stałych wartości wypełnienia, ale w większości przypadków powinno to być wystarczające.


0

talib zawiera proste narzędzie średniej ruchomej, a także inne podobne narzędzia do uśredniania (np. wykładnicza średnia ruchoma). Poniżej porównuje tę metodę z niektórymi innymi rozwiązaniami.


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

Jedynym zastrzeżeniem jest to, że rzeczywistość musi zawierać elementy dtype = float. W przeciwnym razie zgłaszany jest następujący błąd

Wyjątek: rzeczywistość nie jest podwójna


0

Oto szybka implementacja przy użyciu numba (pamiętaj o typach). Zauważ, że zawiera nans, gdzie został przesunięty.

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

Zwraca to nans na początku.
Adam Erickson

0

średnia ruchoma

  • odwróć tablicę w i i po prostu weź średnią z i do n.

  • używaj funkcji list compearing do generowania mini tablic w locie.

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

Używam albo rozwiązania zaakceptowanej odpowiedzi , nieco zmodyfikowanej, aby mieć taką samą długość danych wyjściowych jak dane wejściowe, lub pandaswersji, jak wspomniano w komentarzu do innej odpowiedzi. Podsumowuję tutaj oba z powtarzalnym przykładem do wykorzystania w przyszłości:

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

Porównując poniższe rozwiązanie z rozwiązaniem wykorzystującym sumę numpy, zajmuje to prawie połowę czasu . Dzieje się tak, ponieważ nie musi przechodzić przez całą tablicę, aby obliczyć sumę, a następnie wykonać wszystkie odejmowanie. Ponadto suma może być „ niebezpieczna ”, jeśli tablica jest ogromna, a liczba jest ogromna ( możliwe przepełnienie ). Oczywiście, także tutaj istnieje niebezpieczeństwo, ale przynajmniej sumują się tylko istotne liczby.

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.