Kurs
Einführung
Ein gleitender Durchschnitt, auch Rolling- oder Running Average genannt, wird verwendet, um Zeitreihendaten zu analysieren, indem Durchschnitte verschiedener Teilmengen des gesamten Datensatzes berechnet werden. Da dabei über die Zeit der Durchschnitt des Datensatzes gebildet wird, spricht man auch von Moving Mean (MM) oder Rolling Mean.
Es gibt verschiedene Möglichkeiten, den gleitenden Durchschnitt zu berechnen. Eine davon ist, eine feste Teilmenge aus einer vollständigen Zahlenreihe zu nehmen. Der erste gleitende Durchschnitt wird berechnet, indem der Durchschnitt der ersten festen Teilmenge gebildet wird. Anschließend wird die Teilmenge verschoben, indem du nach vorn rückst zur nächsten festen Teilmenge (der neue Wert kommt hinzu, der älteste Wert der Reihe fällt heraus).
Der gleitende Durchschnitt wird meist bei Zeitreihendaten eingesetzt, um kurzfristige Schwankungen herauszufiltern und gleichzeitig längerfristige Trends sichtbar zu machen.
Beispiele für Zeitreihendaten sind Aktienkurse, Wetterberichte, Luftqualität, Bruttoinlandsprodukt, Beschäftigung usw.
Allgemein glättet der gleitende Durchschnitt die Daten.
Der gleitende Durchschnitt ist die Grundlage vieler Algorithmen, etwa des Autoregressive Integrated Moving Average Model (ARIMA), das Moving Averages nutzt, um Vorhersagen für Zeitreihen zu treffen.
Es gibt verschiedene Arten gleitender Durchschnitte:
-
Einfacher gleitender Durchschnitt (Simple Moving Average, SMA): Der einfache gleitende Durchschnitt nutzt ein gleitendes Fenster, um über eine festgelegte Anzahl von Zeitperioden den Durchschnitt zu bilden. Er ist ein gleich gewichteter Mittelwert der vorherigen n Datenpunkte.
Um SMA besser zu verstehen, nehmen wir eine Folge von n Werten:

Dann ist der gleich gewichtete gleitende Durchschnitt für n Datenpunkte im Kern der Mittelwert der vorherigen M Datenpunkte, wobei M die Größe des gleitenden Fensters ist:

Für die nachfolgenden Werte des gleitenden Durchschnitts wird jeweils ein neuer Wert zur Summe addiert und der älteste Zeitraum entfernt. Da du bereits den Durchschnitt der vorherigen Perioden hast, ist keine vollständige Neusummation nötig:

- Kumulativer gleitender Durchschnitt (Cumulative Moving Average, CMA): Anders als beim einfachen gleitenden Durchschnitt, der die älteste Beobachtung verwirft, sobald eine neue hinzukommt, berücksichtigt der kumulative gleitende Durchschnitt alle bisherigen Beobachtungen. CMA eignet sich weniger gut, um Trends zu analysieren und Daten zu glätten. Der Grund: Er mittelt alle bisherigen Daten bis zum aktuellen Punkt, also ein gleich gewichteter Mittelwert der Folge von n Werten:
bis zum aktuellen Zeitpunkt ergibt sich:
Analog kann die Aktualisierung des kumulativen Durchschnitts für jeden neuen Wert mit folgender Formel berechnet werden:

- Exponentieller gleitender Durchschnitt (Exponential Moving Average, EMA): Im Unterschied zu SMA und CMA gewichtet der exponentielle gleitende Durchschnitt jüngste Werte stärker. Dadurch kann er Trends oft schneller und genauer nachzeichnen. Die Reaktion des EMA ist direkt proportional zum Muster der Daten.
Da EMAs neuere Daten stärker gewichten als ältere, reagieren sie sensibler auf aktuelle Preisänderungen als SMAs. Die Ergebnisse sind dadurch zeitnäher, weshalb EMA oft bevorzugt wird.
Genug Theorie, oder? Lass uns zur praktischen Umsetzung des gleitenden Durchschnitts springen.
Moving Average mit Zeitreihendaten umsetzen
Einfacher gleitender Durchschnitt (SMA)
Erst erstellen wir Dummy-Zeitreihendaten und implementieren SMA nur mit Python.
Angenommen, die Nachfrage nach einem Produkt wird über 12 Monate (1 Jahr) beobachtet. Du sollst gleitende Durchschnitte für Fenster von 3 und 4 Monaten berechnen.
Modul importieren
import pandas as pd
import numpy as np
product = {'month' : [1,2,3,4,5,6,7,8,9,10,11,12],'demand':[290,260,288,300,310,303,329,340,316,330,308,310]}
df = pd.DataFrame(product)
df.head()
Code aus diesem Tutorial online ausführen und bearbeiten
Code ausführen| month | demand | |
|---|---|---|
| 0 | 1 | 290 |
| 1 | 2 | 260 |
| 2 | 3 | 288 |
| 3 | 4 | 300 |
| 4 | 5 | 310 |
Lass uns den SMA für eine Fenstergröße von 3 berechnen. Das bedeutet, du nimmst jeweils drei Werte, um den gleitenden Durchschnitt zu bilden, und bei jedem neuen Wert fällt der älteste weg.
Dafür verwendest du in pandas die Funktion iloc. Da du die Spalte demand brauchst, fixierst du deren Position in iloc, während die Zeile eine Variable i ist, die du iterierst, bis du das Ende des DataFrames erreichst.
for i in range(0,df.shape[0]-2):
df.loc[df.index[i+2],'SMA_3'] = np.round(((df.iloc[i,1]+ df.iloc[i+1,1] +df.iloc[i+2,1])/3),1)
| month | demand | SMA_3 | |
|---|---|---|---|
| 0 | 1 | 290 | NaN |
| 1 | 2 | 260 | NaN |
| 2 | 3 | 288 | 279.3 |
| 3 | 4 | 300 | 282.7 |
| 4 | 5 | 310 | 299.3 |
Zur Plausibilitätsprüfung nutzen wir zusätzlich die eingebaute rolling-Funktion von pandas und prüfen, ob sie mit unserem eigenen Python-SMA übereinstimmt.
df['pandas_SMA_3'] = df.iloc[:,1].rolling(window=3).mean()
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | |
|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 |
| 3 | 4 | 300 | 282.7 | 282.666667 |
| 4 | 5 | 310 | 299.3 | 299.333333 |
Super. Wie du siehst, stimmen eigener und pandas-basierter gleitender Durchschnitt exakt überein. Deine SMA-Implementierung passt.
Berechnen wir schnell auch den einfachen gleitenden Durchschnitt für eine window_size von 4.
for i in range(0,df.shape[0]-3):
df.loc[df.index[i+3],'SMA_4'] = np.round(((df.iloc[i,1]+ df.iloc[i+1,1] +df.iloc[i+2,1]+df.iloc[i+3,1])/4),1)
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | SMA_4 | |
|---|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 | NaN |
| 3 | 4 | 300 | 282.7 | 282.666667 | 284.5 |
| 4 | 5 | 310 | 299.3 | 299.333333 | 289.5 |
df['pandas_SMA_4'] = df.iloc[:,1].rolling(window=4).mean()
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | SMA_4 | pandas_SMA_4 | |
|---|---|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 | NaN | NaN |
| 3 | 4 | 300 | 282.7 | 282.666667 | 284.5 | 284.5 |
| 4 | 5 | 310 | 299.3 | 299.333333 | 289.5 | 289.5 |
Jetzt visualisierst du die berechneten gleitenden Durchschnitte.
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df['demand'],label='data')
plt.plot(df['SMA_3'],label='SMA 3 Months')
plt.plot(df['SMA_4'],label='SMA 4 Months')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x11fe15080>

Kumulativer gleitender Durchschnitt
Ich denke, wir sind bereit für einen echten Datensatz.
Für den kumulativen gleitenden Durchschnitt verwenden wir ein air quality dataset, das du unter diesem Link herunterladen kannst.
df = pd.read_csv("AirQualityUCI/AirQualityUCI.csv", sep = ";", decimal = ",")
df = df.iloc[ : , 0:14]
df.head()
| Date | Time | CO(GT) | PT08.S1(CO) | NMHC(GT) | C6H6(GT) | PT08.S2(NMHC) | NOx(GT) | PT08.S3(NOx) | NO2(GT) | PT08.S4(NO2) | PT08.S5(O3) | T | RH | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10/03/2004 | 18.00.00 | 2.6 | 1360.0 | 150.0 | 11.9 | 1046.0 | 166.0 | 1056.0 | 113.0 | 1692.0 | 1268.0 | 13.6 | 48.9 |
| 1 | 10/03/2004 | 19.00.00 | 2.0 | 1292.0 | 112.0 | 9.4 | 955.0 | 103.0 | 1174.0 | 92.0 | 1559.0 | 972.0 | 13.3 | 47.7 |
| 2 | 10/03/2004 | 20.00.00 | 2.2 | 1402.0 | 88.0 | 9.0 | 939.0 | 131.0 | 1140.0 | 114.0 | 1555.0 | 1074.0 | 11.9 | 54.0 |
| 3 | 10/03/2004 | 21.00.00 | 2.2 | 1376.0 | 80.0 | 9.2 | 948.0 | 172.0 | 1092.0 | 122.0 | 1584.0 | 1203.0 | 11.0 | 60.0 |
| 4 | 10/03/2004 | 22.00.00 | 1.6 | 1272.0 | 51.0 | 6.5 | 836.0 | 131.0 | 1205.0 | 116.0 | 1490.0 | 1110.0 | 11.2 | 59.6 |
Vorverarbeitung ist ein entscheidender Schritt, wenn du mit Daten arbeitest. Für numerische Daten gehört das Prüfen auf NaN (Null)-Werte zu den gängigen Schritten. Falls es NaN-Werte gibt, kannst du sie z. B. durch 0, den Durchschnitt, vorherige oder nachfolgende Werte ersetzen oder die Zeilen sogar löschen. Ersetzen ist meist besser als löschen. Da dieser Datensatz jedoch nur wenige NULL-Werte hat, beeinträchtigt das Löschen die Kontinuität der Reihe nicht.
df.isna().sum()
Date 114
Time 114
CO(GT) 114
PT08.S1(CO) 114
NMHC(GT) 114
C6H6(GT) 114
PT08.S2(NMHC) 114
NOx(GT) 114
PT08.S3(NOx) 114
NO2(GT) 114
PT08.S4(NO2) 114
PT08.S5(O3) 114
T 114
RH 114
dtype: int64
Aus der Ausgabe oben siehst du, dass es rund 114 NaN-Werte über alle Spalten gibt. Sie liegen jedoch alle am Ende der Zeitreihe, daher können wir sie schnell entfernen.
df.dropna(inplace=True)
df.isna().sum()
Date 0
Time 0
CO(GT) 0
PT08.S1(CO) 0
NMHC(GT) 0
C6H6(GT) 0
PT08.S2(NMHC) 0
NOx(GT) 0
PT08.S3(NOx) 0
NO2(GT) 0
PT08.S4(NO2) 0
PT08.S5(O3) 0
T 0
RH 0
dtype: int64
Wir wenden den kumulativen gleitenden Durchschnitt auf die Temperaturspalte (T) an. Trennen wir diese Spalte schnell vom restlichen Datensatz ab.
df_T = pd.DataFrame(df.iloc[:,-2])
df_T.head()
| T | |
|---|---|
| 0 | 13.6 |
| 1 | 13.3 |
| 2 | 11.9 |
| 3 | 11.0 |
| 4 | 11.2 |
Jetzt verwenden wir die pandas-Methode expanding, um den kumulativen Durchschnitt der obigen Daten zu berechnen. Zur Erinnerung: Anders als der einfache gleitende Durchschnitt berücksichtigt der kumulative gleitende Durchschnitt alle vorangegangenen Werte.
df_T['CMA_4'] = df_T.expanding(min_periods=4).mean()
df_T.head(10)
| T | CMA_4 | |
|---|---|---|
| 0 | 13.6 | NaN |
| 1 | 13.3 | NaN |
| 2 | 11.9 | NaN |
| 3 | 11.0 | 12.450000 |
| 4 | 11.2 | 12.200000 |
| 5 | 11.2 | 12.033333 |
| 6 | 11.3 | 11.928571 |
| 7 | 10.7 | 11.775000 |
| 8 | 10.7 | 11.655556 |
| 9 | 10.3 | 11.520000 |
Zeitreihendaten werden gegen die Zeit aufgetragen. Kombinieren wir also die Spalten Datum und Uhrzeit und wandeln sie in ein Datetime-Objekt um. Dafür nutzt du das Python-Modul datetime (Quelle: Time Series Tutorial).
import datetime
df['DateTime'] = (df.Date) + ' ' + (df.Time)
df.DateTime = df.DateTime.apply(lambda x: datetime.datetime.strptime(x, '%d/%m/%Y %H.%M.%S'))
Ändern wir nun den Index des temperature-DataFrames auf Datetime.
df_T.index = df.DateTime
Jetzt plotten wir die tatsächliche Temperatur und den kumulativen gleitenden Durchschnitt über die Zeit.
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df_T['T'],label='temperature')
plt.plot(df_T['CMA_4'],label='CMA_4')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x1210a2d30>

Exponentieller gleitender Durchschnitt
df_T['EMA'] = df_T.iloc[:,0].ewm(span=40,adjust=False).mean()
df_T.head()
| T | CMA_4 | EMA | |
|---|---|---|---|
| DateTime | |||
| 2004-03-10 18:00:00 | 13.6 | NaN | 13.600000 |
| 2004-03-10 19:00:00 | 13.3 | NaN | 13.585366 |
| 2004-03-10 20:00:00 | 11.9 | NaN | 13.503153 |
| 2004-03-10 21:00:00 | 11.0 | 12.45 | 13.381048 |
| 2004-03-10 22:00:00 | 11.2 | 12.20 | 13.274655 |
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df_T['T'],label='temperature')
plt.plot(df_T['CMA_4'],label='CMA_4')
plt.plot(df_T['EMA'],label='EMA')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x14b2a41d0>

Wow! Wie du im obigen Diagramm siehst, bildet der Exponentielle gleitende Durchschnitt (EMA) das Muster der Daten hervorragend ab, während der Kumulative gleitende Durchschnitt (CMA) deutlich hinterherhinkt.
Geh noch weiter!
Glückwunsch, du hast das Tutorial abgeschlossen.
Dieses Tutorial war ein guter Einstieg, um die gleitenden Durchschnitte deiner Daten zu berechnen und sinnvoll zu interpretieren.
Versuche, den kumulativen und den exponentiellen gleitenden Durchschnitt in Python ohne die pandas-Bibliothek zu programmieren. Das gibt dir ein tieferes Verständnis dafür, wie sie berechnet werden und worin sie sich unterscheiden.
Es gibt noch viel zu experimentieren. Berechne zum Beispiel die partielle Autokorrelation zwischen den Eingangsdaten und dem gleitenden Durchschnitt und suche nach Zusammenhängen.
Wenn du mehr über DataFrames in pandas lernen möchtest, schau dir den interaktiven Kurs pandas Foundations von DataCamp an.
Referenzen:
Stell gern deine Fragen zu diesem Tutorial unten in den Kommentaren.