Cursus
Introductie
Een moving average, ook wel een rolling of running average genoemd, wordt gebruikt om tijdreeksgegevens te analyseren door gemiddelden te berekenen van verschillende deelverzamelingen van de volledige dataset. Omdat het gaat om het nemen van het gemiddelde van de dataset in de tijd, wordt het ook wel een moving mean (MM) of rolling mean genoemd.
Er zijn verschillende manieren om het rolling average te berekenen, maar één manier is om een vaste deelverzameling uit een volledige reeks getallen te nemen. Het eerste moving average wordt berekend door het gemiddelde te nemen van de eerste vaste deelverzameling getallen, en vervolgens wordt de deelverzameling aangepast door vooruit te schuiven naar de volgende vaste deelverzameling (waarbij de toekomstige waarde in de subgroep wordt opgenomen en het vorige getal uit de reeks wordt weggelaten).
Het moving average wordt meestal gebruikt bij tijdreeksen om kortetermijnschommelingen te dempen en tegelijkertijd op langere trends te focussen.
Een paar voorbeelden van tijdreeksgegevens zijn aandelenkoersen, weerberichten, luchtkwaliteit, bruto binnenlands product, werkgelegenheid, enz.
In het algemeen maakt het moving average de gegevens vloeiender.
Moving average is de ruggengraat van veel algoritmen; een daarvan is het Autoregressive Integrated Moving Average Model (ARIMA), dat moving averages gebruikt om voorspellingen te doen voor tijdreeksgegevens.
Er zijn verschillende soorten moving averages:
-
Simple Moving Average (SMA): Simple Moving Average (SMA) gebruikt een schuivend venster om het gemiddelde te nemen over een vast aantal perioden. Het is een gelijk gewogen gemiddelde van de vorige n gegevens.
Om SMA beter te begrijpen, nemen we een voorbeeld, een reeks van n waarden:

dan is het gelijk gewogen rolling average voor n datapunten in wezen het gemiddelde van de vorige M datapunten, waarbij M de grootte van het schuivende venster is:

Op dezelfde manier wordt voor het berekenen van opvolgende rolling average-waarden een nieuwe waarde aan de som toegevoegd en de waarde van de vorige periode weggelaten; omdat je het gemiddelde van de vorige perioden hebt, is een volledige som elke keer niet nodig:

- Cumulative Moving Average (CMA): In tegenstelling tot simple moving average, waarbij de oudste observatie wordt verwijderd wanneer er een nieuwe wordt toegevoegd, neemt cumulative moving average alle eerdere observaties mee. CMA is niet zo'n goede techniek om trends te analyseren en de gegevens te vergladden. De reden is dat het al de eerdere gegevens tot aan het huidige datapunt middelt, dus een gelijk gewogen gemiddelde van de reeks van n waarden:
tot aan het huidige tijdstip wordt gegeven door:
Op vergelijkbare wijze kan het bijwerken van het cumulatieve gemiddelde voor elke nieuwe waarde worden berekend met de onderstaande formule:

- Exponential Moving Average (EMA): In tegenstelling tot SMA en CMA geeft exponential moving average meer gewicht aan recente prijzen en kan het daardoor een beter model zijn of de trendbeweging sneller vastleggen. De reactie van de EMA is recht evenredig met het patroon van de gegevens.
Omdat EMA's een hoger gewicht geven aan recente gegevens dan aan oudere gegevens, reageren ze sneller op de laatste prijsveranderingen dan SMA's. Daardoor zijn de resultaten van EMA's actueler en wordt EMA vaker verkozen boven andere technieken.
Genoeg theorie, toch? Laten we naar de praktische implementatie van het moving average gaan.
Moving average implementeren op tijdreeksgegevens
Simple Moving Average (SMA)
We maken eerst dummy-tijdreeksgegevens en proberen SMA te implementeren met alleen Python.
Stel dat er vraag is naar een product en dat die 12 maanden (1 jaar) wordt geobserveerd, en je moet moving averages vinden voor vensterperioden van 3 en 4 maanden.
Module importeren
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()
Voer de code van deze tutorial online uit en bewerk deze
Code uitvoeren| month | demand | |
|---|---|---|
| 0 | 1 | 290 |
| 1 | 2 | 260 |
| 2 | 3 | 288 |
| 3 | 4 | 300 |
| 4 | 5 | 310 |
Laten we SMA berekenen voor een venstergrootte van 3. Dat betekent dat je telkens drie waarden meeneemt om het moving average te berekenen, en dat bij elke nieuwe waarde de oudste waarde wordt genegeerd.
Om dit te implementeren, gebruik je de pandas-functie iloc. Omdat je de kolom demand nodig hebt, fixeer je de positie daarvan in de iloc-functie, terwijl de rij een variabele i is die je blijft itereren totdat je het einde van de dataframe bereikt.
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 |
Voor een sanity check gebruiken we ook de ingebouwde rolling-functie van pandas en kijken we of die overeenkomt met onze zelfgeschreven Python-gebaseerde simple moving average.
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 |
Top, zoals je ziet komen de custom en pandas moving averages precies overeen, wat betekent dat je implementatie van SMA correct was.
Laten we ook snel het simple moving average berekenen voor een window_size van 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 |
Nu ga je de gegevens plotten van de moving averages die je hebt berekend.
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>

Cumulative Moving Average
Ik denk dat we nu klaar zijn voor een echte dataset.
Voor cumulative moving average gebruiken we een luchtkwaliteitsdataset die je via deze link kunt downloaden.
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 |
Preprocessing is een essentiële stap wanneer je met data werkt. Voor numerieke gegevens is een van de meest voorkomende preprocessing-stappen het controleren op NaN (Null)-waarden. Als er NaN-waarden zijn, kun je die vervangen door 0, het gemiddelde, voorafgaande of daaropvolgende waarden, of ze zelfs droppen. Hoewel vervangen normaal gesproken een betere keuze is dan droppen, heeft deze dataset weinig NULL-waarden, dus ze droppen zal de continuïteit van de reeks niet beïnvloeden.
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
Uit de bovenstaande output kun je opmaken dat er ongeveer 114 NaN-waarden zijn over alle kolommen. Je zult echter zien dat ze allemaal aan het einde van de tijdreeks staan, dus laten we ze snel droppen.
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
Je gaat het cumulative moving average toepassen op de Temperatuur-kolom (T), dus laten we die kolom snel loshalen van de volledige data.
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 |
Nu gebruik je de pandas-methode expanding om het cumulatieve gemiddelde van de bovenstaande gegevens te vinden. Zoals je je uit de introductie herinnert, neemt het cumulative moving average, in tegenstelling tot het simple moving average, alle voorgaande waarden mee bij het berekenen van het gemiddelde.
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 |
Tijdreeksgegevens worden uitgezet ten opzichte van de tijd, dus laten we de datum- en tijdkolom combineren en omzetten naar een datetime-object. Hiervoor gebruik je de module datetime uit Python (Bron: 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'))
Laten we de index van de temperature-dataframe wijzigen naar datetime.
df_T.index = df.DateTime
Laten we nu de werkelijke temperatuur en het cumulative moving average uitzetten t.o.v. de tijd.
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>

Exponential Moving Average
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>

Wauw! Zoals je in de bovenstaande grafiek kunt zien, doet de Exponential Moving Average (EMA) het uitstekend in het vastleggen van het patroon in de gegevens, terwijl de Cumulative Moving Average (CMA) aanzienlijk achterblijft.
Ga verder!
Gefeliciteerd met het afronden van de tutorial.
Deze tutorial was een goed startpunt om te leren hoe je de moving averages van je gegevens kunt berekenen en er betekenis aan kunt geven.
Probeer de Python-code voor cumulative en exponential moving average te schrijven zonder de pandas-bibliotheek. Dat geeft je veel diepere kennis over hoe ze worden berekend en op welke manieren ze van elkaar verschillen.
Er valt nog veel te experimenteren. Probeer de partiële autocorrelatie te berekenen tussen de inputgegevens en het moving average, en probeer daar een relatie tussen te vinden.
Als je meer wilt leren over DataFrames in pandas, volg dan de interactieve cursus pandas Foundations van DataCamp.
Referenties:
Stel gerust vragen over deze tutorial in de reacties hieronder.
