Wie berechnet man den gleitenden Durchschnitt mit NumPy?

109

Es scheint keine Funktion zu geben, die einfach den gleitenden Durchschnitt für Numpy / Scipy berechnet, was zu verschlungenen Lösungen führt .

Meine Frage ist zweifach:

  • Was ist der einfachste Weg, um einen gleitenden Durchschnitt mit numpy (richtig) zu implementieren?
  • Gibt es einen guten Grund, die Batterien in diesem Fall nicht zu verwenden, da dies nicht trivial und fehleranfällig erscheint ?
Goncalopp
quelle
19
Die Faltungslösung scheint mir nicht so kompliziert zu sein!
wim
4
Ist ein gleitender Durchschnitt nicht nur ein Tiefpassfilter (dh "Unschärfe")? Ziemlich sicher, dass genau das ist, wofür Faltung gedacht ist ...
user541686
@mmgp Ich glaube, ich hatte gehofft, falsch zu liegen, oder dass es einen guten, offensichtlichen Grund gab.
Goncalopp
3
@wim Es war halb als Wortspiel gedacht. Die bloße Tatsache, dass die Frage existiert, bedeutet jedoch, dass es nicht einfach ist, aus numpy.convolute einen gleitenden Durchschnitt zu erstellen.
Goncalopp

Antworten:

162

Wenn Sie nur eine einfache wollen nicht gewichteten Durchschnitt bewegen, können Sie leicht implementieren es mit np.cumsum, was sein kann , ist schneller als FFT basierte Methoden:

BEARBEITEN Eine von Bean im Code entdeckte falsche Indizierung wurde korrigiert. BEARBEITEN

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

Die Antwort lautet also: Es ist wirklich einfach zu implementieren, und vielleicht ist Numpy bereits ein wenig aufgebläht mit speziellen Funktionen.

Jaime
quelle
10
Dieser Code ist falsch. zB Moving_average ([1,2,5,10], n = 2) ergibt [1., 3.5, 8.5]. Selbst der Testfall des Antwortenden für einen gleitenden Durchschnitt von Werten von 0 bis 19 ist falsch und behauptet, dass der Durchschnitt von 0, 1 und 2 0,5 beträgt. Wie kam es zu 6 Upvotes?
JeremyKun
2
Vielen Dank für die Fehlerprüfung, es scheint jetzt gut zu funktionieren. Was die positiven Stimmen angeht, so schätze ich, dass die allgemeine Idee hinter der Antwort schwerer abgewogen wurde als ein Fehler bei der Implementierung, aber wer weiß.
Jaime
2
Ich habe das Problem gefunden. ret[n:] -= ret[:-n]ist nicht das gleiche wie ret[n:] = ret[n:] - ret[:-n]. Ich habe den Code in dieser Antwort korrigiert. Edit: Nein, jemand anderes hat mich einfach geschlagen.
Timmmm
7
@Timmmm habe ich, das war ja das Problem. Das allgemeine Prinzip hinter dieser Antwort ist in der Bildverarbeitung weit verbreitet (summierte Flächentabellen, die sie nennen), daher musste das Problem in der Implementierung liegen. Ein gutes Beispiel dafür, wie man durch vorzeitige Optimierung gebissen wird, da ich mich daran erinnere, dass ich die Operation direkt durchgeführt habe, "weil sie effizienter sein wird". Auf der positiven Seite hat es wahrscheinlich schneller die falsche Antwort hervorgebracht ...
Jaime
43
Hmmm, es scheint, dass diese "einfach zu implementierende" Funktion eigentlich ziemlich leicht falsch zu verstehen ist und eine gute Diskussion über die Speichereffizienz gefördert hat. Ich bin froh, aufgebläht zu sein, wenn es bedeutet zu wissen, dass etwas richtig gemacht wurde.
Richard
81

Das Fehlen einer bestimmten domänenspezifischen Funktion durch NumPy ist möglicherweise auf die Disziplin und die Treue des Kernteams zur Hauptanweisung von NumPy zurückzuführen: Bereitstellung eines N-dimensionalen Array-Typs sowie Funktionen zum Erstellen und Indizieren dieser Arrays. Wie viele grundlegende Ziele ist auch dieses nicht klein, und NumPy macht es hervorragend.

Das (viel) größere SciPy enthält eine viel größere Sammlung domänenspezifischer Bibliotheken ( von SciPy-Entwicklern als Unterpakete bezeichnet) - zum Beispiel numerische Optimierung ( Optimierung ), Signalverarbeitung ( Signal ) und Integralrechnung ( Integration ).

Ich vermute, dass die Funktion, nach der Sie suchen, in mindestens einem der SciPy-Unterpakete enthalten ist ( scipy.signal vielleicht). Ich würde jedoch zuerst in der Sammlung der SciPy-Scikits nachsehen , die relevanten Scikits identifizieren und dort nach der interessierenden Funktion suchen.

Scikits sind unabhängig entwickelte Pakete, die auf NumPy / SciPy basieren und auf eine bestimmte technische Disziplin ausgerichtet sind (z. B. Scikits-Image , Scikits-Learn usw.). Einige davon waren (insbesondere das großartige OpenOpt für die numerische Optimierung) hoch angesehen. reife Projekte lange bevor sie sich für die relativ neue Rubrik Scikits entschieden haben . Auf der Scikits- Homepage wurden oben etwa 30 solcher Scikits aufgeführt , von denen sich jedoch mindestens einige nicht mehr in der aktiven Entwicklung befinden.

Das Befolgen dieses Ratschlags würde Sie zu Scikits-Zeitreihen führen ; Dieses Paket befindet sich jedoch nicht mehr in der aktiven Entwicklung. In der Tat Pandas geworden, AFAIK, die de facto NumPy Zeitreihe Bibliothek -basierte.

Pandas verfügt über mehrere Funktionen, mit denen ein gleitender Durchschnitt berechnet werden kann . Das einfachste davon ist wahrscheinlich rollendes Mittel , das Sie wie folgt verwenden:

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

Rufen Sie jetzt einfach die Funktion rolling_mean auf, die das Series-Objekt und eine Fenstergröße übergibt , die in meinem Beispiel unten 10 Tage beträgt .

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

Vergewissern Sie sich, dass es funktioniert hat - z. B. verglichen Sie die Werte 10 bis 15 in der Originalserie mit der neuen Serie, die mit dem rollierenden Mittelwert geglättet wurde

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

Die Funktion rolling_mean sowie etwa ein Dutzend anderer Funktionen sind in der Pandas-Dokumentation informell unter den Funktionen für das Verschieben von Fenstern in der Rubrik zusammengefasst . Eine zweite verwandte Gruppe von Funktionen in Pandas wird als exponentiell gewichtete Funktionen bezeichnet (z. B. ewma , das den exponentiell gleitenden gewichteten Durchschnitt berechnet). Die Tatsache, dass diese zweite Gruppe nicht in der ersten Gruppe enthalten ist ( Funktionen zum Verschieben von Fenstern ), liegt möglicherweise daran, dass die exponentiell gewichteten Transformationen nicht auf einem Fenster fester Länge beruhen

Doug
quelle
6
Pandas hat eine starke Reihe von Funktionen für bewegliche Fenster. Aber es scheint mir ein wenig zu viel Aufwand für einen einfachen gleitenden Durchschnitt zu sein.
Jaime
6
Nun, ich bezweifle, dass die Berechnung eines gleitenden Durchschnitts eine isolierte Anforderung für das OP oder für nahezu jeden anderen ist. Wenn Sie einen gleitenden Durchschnitt berechnen müssen, haben Sie mit ziemlicher Sicherheit eine Zeitreihe. Dies bedeutet, dass Sie eine Datenstruktur benötigen, mit der Sie einen Datums- / Zeitindex an Ihre Daten anpassen können. Dies ist der „Overhead“, auf den Sie sich beziehen.
Doug
2
Zunächst einmal vielen Dank, dass Sie sich die Zeit genommen haben, diese äußerst informative Antwort zu schreiben. In der Tat kann ich keine Verwendung für einen gleitenden Durchschnitt sehen, der keine Zeitreihen umfasst. Das heißt aber nicht, dass man es an eine Datumszeit oder sogar an ein bestimmtes Abtastintervall anpassen muss (es kann unbekannt sein)
Goncalopp
3
Ich wollte nur hinzufügen, dass die Funktion für den gleitenden Durchschnitt in die Engpassbibliothek extrahiert wurde, wenn Pandas als Abhängigkeit zu schwer erscheinen.
Robochat
4
'rollendes_Mittel' ist nicht mehr Teil von Pandas, siehe Antwort mit 'Rollen' stattdessen
Vladtn
57

Ein einfacher Weg, dies zu erreichen, ist die Verwendung von np.convolve. Die Idee dahinter ist, die Art und Weise, wie die diskrete Faltung berechnet wird, zu nutzen und daraus einen rollierenden Mittelwert zurückzugeben . Dies kann durch Falten mit einer Folge von np.oneseiner Länge erfolgen, die der gewünschten Schiebefensterlänge entspricht.

Dazu könnten wir folgende Funktion definieren:

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

Diese Funktion übernimmt die Faltung der Sequenz xund eine Sequenz von Längen w. Beachten Sie, dass das gewählte so modeist valid, dass das Faltungsprodukt nur für Punkte angegeben wird, an denen sich die Sequenzen vollständig überlappen.


Einige Beispiele:

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

Für einen gleitenden Durchschnitt mit einem Fenster Länge hätten 2wir:

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

Und für ein Fenster von Länge 4:

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

Wie funktioniert das convolve?

Schauen wir uns die Art und Weise, wie die diskrete Faltung berechnet wird, genauer an. Die folgende Funktion zielt darauf ab, die Art und Weise der np.convolveBerechnung der Ausgabewerte zu replizieren :

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

Was für dasselbe Beispiel oben auch ergeben würde:

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

Was also bei jedem Schritt getan wird, ist, das innere Produkt zwischen dem Array von Einsen und dem aktuellen Fenster zu nehmen . In diesem Fall ist die Multiplikation mit np.ones(w)überflüssig, da wir sumdie Sequenz direkt nehmen .

Unten sehen Sie ein Beispiel dafür, wie die ersten Ausgaben so berechnet werden, dass sie etwas klarer sind. Nehmen wir an, wir wollen ein Fenster von 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

Und die folgende Ausgabe würde wie folgt berechnet:

  [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

Und so weiter, indem ein gleitender Durchschnitt der Sequenz zurückgegeben wird, sobald alle Überlappungen durchgeführt wurden.

Yatu
quelle
Das ist eine schöne Idee! Es ist schneller als die Antwort von @ Jaime für kleines n, wird jedoch langsamer für größeres n.
Felipe Gerard
Danke @FelipeGerard! Ja, wie in den Kommentaren ausgeführt, obwohl dieser Ansatz möglicherweise nicht so effizient ist wie einige andere numpy-Lösungen, ist es aufgrund seiner Einfachheit und Prägnanz eine Alternative für zukünftige Besucher
yatu
Manchmal ist es nützlich, ein Ausgabearray mit der gleichen Größe wie die Eingabe zu haben. Hierzu mode='valid'kann das durch ersetzt werden 'same'. Nur in diesem Fall werden Kantenpunkte gegen Null verschoben.
Ilia Barahovski
15

Hier finden Sie verschiedene Möglichkeiten, dies zusammen mit einigen Benchmarks. Die besten Methoden sind Versionen, die optimierten Code aus anderen Bibliotheken verwenden. Die bottleneck.move_meanMethode ist wahrscheinlich rundum am besten. Der scipy.convolveAnsatz ist auch sehr schnell, erweiterbar und syntaktisch und konzeptionell einfach, lässt sich jedoch nicht gut für sehr große Fensterwerte skalieren. Die numpy.cumsumMethode ist gut, wenn Sie einen reinen numpyAnsatz benötigen .

Hinweis: Einige davon (z. B. bottleneck.move_mean) sind nicht zentriert und verschieben Ihre Daten.

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)

Timing, kleines Fenster (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)

Timing, großes Fenster (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)

Speicher, kleines Fenster (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

Speicher, großes Fenster (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
argentum2f
quelle
11

Diese Antwort mit Pandas wird von oben angepasst, da sie rolling_meannicht mehr Teil von Pandas ist

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

Rufen Sie jetzt einfach die Funktion rollingauf dem Datenrahmen mit einer Fenstergröße auf, die in meinem Beispiel unten 10 Tage beträgt.

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
Vladtn
quelle
5

Ich bin der Meinung, dass dies mit Engpässen leicht gelöst werden kann

Siehe Basisbeispiel unten:

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)

Dies ergibt einen Bewegungsmittelwert entlang jeder Achse.

  • "mm" ist das gleitende Mittel für "a".

  • "Fenster" ist die maximale Anzahl von Einträgen, die für den gleitenden Mittelwert berücksichtigt werden müssen.

  • "min_count" ist die minimale Anzahl von Einträgen, die für den gleitenden Mittelwert berücksichtigt werden müssen (z. B. für das erste Element oder wenn das Array Nanowerte hat).

Der gute Teil ist, dass Engpass beim Umgang mit Nanowerten hilft und auch sehr effizient ist.

Anthony Anyanwu
quelle
2

Wenn Sie die Randbedingungen sorgfältig berücksichtigen möchten ( Mittelwert nur aus verfügbaren Elementen an den Kanten berechnen ), reicht die folgende Funktion aus.

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])
Peixiang Zhong
quelle
1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

Probieren Sie diesen Code aus. Ich denke, es ist einfacher und macht den Job. Lookback ist das Fenster des gleitenden Durchschnitts.

In der habe Data[i-lookback:i, 0].sum()ich mich 0auf die erste Spalte des Datensatzes bezogen, aber Sie können eine beliebige Spalte einfügen, falls Sie mehr als eine Spalte haben.

Sofien Kaabar
quelle
0

Ich wollte eigentlich ein etwas anderes Verhalten als die akzeptierte Antwort. Ich habe einen Feature-Extraktor für den gleitenden Durchschnitt für eine sklearnPipeline erstellt, daher musste die Ausgabe des gleitenden Durchschnitts dieselbe Dimension wie die Eingabe haben. Ich möchte, dass der gleitende Durchschnitt davon ausgeht, dass die Reihe konstant bleibt, dh ein gleitender Durchschnitt von [1,2,3,4,5]mit Fenster 2 würde ergeben [1.5,2.5,3.5,4.5,5.0].

Für Spaltenvektoren (mein Anwendungsfall) erhalten wir

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

Und für Arrays

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

Natürlich muss man keine konstanten Werte für die Polsterung annehmen, aber dies sollte in den meisten Fällen ausreichend sein.

cbartondock
quelle
0

talib enthält ein einfaches gleitendes Durchschnittswerkzeug sowie andere ähnliche Mittelungswerkzeuge (dh einen exponentiellen gleitenden Durchschnitt). Im Folgenden wird die Methode mit einigen anderen Lösungen verglichen.


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

Eine Einschränkung ist, dass das Reale Elemente von haben muss dtype = float. Andernfalls wird der folgende Fehler ausgelöst

Ausnahme: real ist nicht doppelt

Josmoor98
quelle
0

Hier ist eine schnelle Implementierung mit numba (beachten Sie die Typen). Beachten Sie, dass es Nans enthält, die verschoben wurden.

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())) 
Mott das Tupel
quelle
Dies gibt am Anfang nans zurück.
Adam Erickson
0

gleitender Durchschnitt

  • kehre das Array bei i um und nimm einfach den Mittelwert von i nach n.

  • Verwenden Sie das Listenverständnis, um Mini-Arrays im laufenden Betrieb zu generieren.

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)
inivri
quelle
0

Ich verwende entweder die Lösung der akzeptierten Antwort , die leicht modifiziert wurde, um die gleiche Länge für die Ausgabe wie die Eingabe zu haben, oder pandasdie Version, wie in einem Kommentar einer anderen Antwort erwähnt. Ich fasse beide hier mit einem reproduzierbaren Beispiel zum späteren Nachschlagen zusammen:

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       ]
Basj
quelle
0

Wenn Sie die unten stehende Lösung mit der Lösung vergleichen, die Cumum of Numpy verwendet, dauert diese fast die Hälfte der Zeit . Dies liegt daran, dass nicht das gesamte Array durchlaufen werden muss, um die Cumsum und dann die gesamte Subtraktion durchzuführen. Darüber hinaus kann das Cumsum " gefährlich " sein, wenn das Array riesig und die Anzahl riesig ist ( möglicher Überlauf ). Natürlich besteht auch hier die Gefahr, aber zumindest werden nur die wesentlichen Zahlen zusammengefasst.

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
Vittorio Carmignani
quelle