Kolminkertainen eksponentiaalinen tasoitus

Kolminkertaista eksponentiaalista tasoitusta kutsutaan myös Holt-Winterin malliksi.

Holt-Winterin malli huomioi sekä trendin että kausivaihtelun.

Eksponentiaalinen tasoitus löytyy statsmodels.tsa-kirjastosta.

Mallin hyvyyttä kuvaava statistiikka löytyy sklearn.metrics-kirjastosta.

Lisätietoa https://tilastoapu.wordpress.com/2018/08/30/aikasarjaennustaminen-3/

Aikasarjaan tutustuminen

In [1]:
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
# Tiedoston avaaminen
df = pd.read_excel('http://taanila.fi/aikasarja.xlsx')
df
Out[2]:
Vuosineljännes Kysyntä
0 2013-12-31 500
1 2014-03-31 350
2 2014-06-30 250
3 2014-09-30 400
4 2014-12-31 450
5 2015-03-31 350
6 2015-06-30 200
7 2015-09-30 300
8 2015-12-31 350
9 2016-03-31 200
10 2016-06-30 150
11 2016-09-30 400
12 2016-12-31 550
13 2017-03-31 350
14 2017-06-30 250
15 2017-09-30 550
16 2017-12-31 550
17 2018-03-31 400
18 2018-06-30 350
19 2018-09-30 600
20 2018-12-31 750
21 2019-03-31 500
22 2019-06-30 400
23 2019-09-30 650
24 2019-12-31 850
In [3]:
# Aikaleimat indeksiin
# to_datetime muuntaa merkkijonomuotoisen tiedon aikaleimoiksi
# format mahdollistaa erilaisten esitysmuotojen tunnistamisen aikaleimoiksi
df.index = pd.to_datetime(df['Vuosineljännes'], format = "%Y-%m-%d")

# Pudotetaan tarpeettomaksi käynyt sarake pois
df = df.drop('Vuosineljännes', axis = 1)
df
Out[3]:
Kysyntä
Vuosineljännes
2013-12-31 500
2014-03-31 350
2014-06-30 250
2014-09-30 400
2014-12-31 450
2015-03-31 350
2015-06-30 200
2015-09-30 300
2015-12-31 350
2016-03-31 200
2016-06-30 150
2016-09-30 400
2016-12-31 550
2017-03-31 350
2017-06-30 250
2017-09-30 550
2017-12-31 550
2018-03-31 400
2018-06-30 350
2018-09-30 600
2018-12-31 750
2019-03-31 500
2019-06-30 400
2019-09-30 650
2019-12-31 850
In [4]:
# Aikasarja viivakaaviona
df.plot()
Out[4]:
<matplotlib.axes._subplots.AxesSubplot at 0x242884b09e8>
In [5]:
# Aikasarjan vaihtelua aiheuttavien komponenttien erottelu
# Observed=alkuperäinen aikasarja, Trend=trendi, Seasonal=kausivaihtelu,
# Residual=muu kuin trendiin ja kausivaihteluun liittyvä vaihtelu
# Jos kaaviot tulostuvat kahteen kertaan, niin siitä ei kannata huolestua

from statsmodels.tsa.api import seasonal_decompose

seasonal_decompose(df['Kysyntä']).plot()
Out[5]:

Mallin sovitus

Ennustemalli sovitetaan (fit()) dataan. Tuloksena saadaan olio (tässä olen antanut oliolle nimeksi malli), joka sisältää monenlaista tietoa mallista.

Lisätietoa freq-parametrin mahdollisista arvoista https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases

In [6]:
from statsmodels.tsa.api import ExponentialSmoothing

# Trendi ja kausivaihtelu huomioidaan
# Kausivaihtelu huomoidaan tulomallina (mul), mutta myös summamalli olisi mahdollinen (add)
# seasonal_periods = kuinka monen periodin jaksoissa kausivaihtelu esiintyy?
# freq='Q': aikaleimat vuosineljänneksen viimeisiä päiviä
malli = ExponentialSmoothing(df['Kysyntä'], trend = 'add', seasonal = 'mul', 
                             seasonal_periods = 4, freq='Q').fit()
C:\Users\taaak\AppData\Local\Continuum\anaconda3\lib\site-packages\statsmodels\tsa\holtwinters.py:725: RuntimeWarning: invalid value encountered in less_equal
  loc = initial_p <= lb
C:\Users\taaak\AppData\Local\Continuum\anaconda3\lib\site-packages\statsmodels\tsa\holtwinters.py:731: RuntimeWarning: invalid value encountered in greater_equal
  loc = initial_p >= ub
In [7]:
# malli-olion avulla saadaan mallin mukaan simuloidut ennusteet (fittedvalues) jo toteutuneille ajankohdille
df['Ennuste'] = malli.fittedvalues
df
Out[7]:
Kysyntä Ennuste
Vuosineljännes
2013-12-31 500 500.342943
2014-03-31 350 343.399570
2014-06-30 250 264.996683
2014-09-30 400 447.003743
2014-12-31 450 497.038058
2015-03-31 350 314.156346
2015-06-30 200 262.197606
2015-09-30 300 371.504841
2015-12-31 350 382.323669
2016-03-31 200 246.320936
2016-06-30 150 159.554132
2016-09-30 400 273.680070
2016-12-31 550 470.356433
2017-03-31 350 369.763731
2017-06-30 250 267.520184
2017-09-30 550 447.568015
2017-12-31 550 651.762938
2018-03-31 400 385.451892
2018-06-30 350 301.071964
2018-09-30 600 604.816435
2018-12-31 750 727.515926
2019-03-31 500 507.832724
2019-06-30 400 376.886213
2019-09-30 650 696.642013
2019-12-31 850 793.197655
In [8]:
# Alkuperäinen aikasarja ja mallin mukaiset ennusteet samaan kaavioon
df.plot()
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x2428aba19e8>
In [9]:
# Ennustevirheet (residuaalit) löytyvät malli-oliosta
df['Ennustevirhe'] = malli.resid
df
Out[9]:
Kysyntä Ennuste Ennustevirhe
Vuosineljännes
2013-12-31 500 500.342943 -0.342943
2014-03-31 350 343.399570 6.600430
2014-06-30 250 264.996683 -14.996683
2014-09-30 400 447.003743 -47.003743
2014-12-31 450 497.038058 -47.038058
2015-03-31 350 314.156346 35.843654
2015-06-30 200 262.197606 -62.197606
2015-09-30 300 371.504841 -71.504841
2015-12-31 350 382.323669 -32.323669
2016-03-31 200 246.320936 -46.320936
2016-06-30 150 159.554132 -9.554132
2016-09-30 400 273.680070 126.319930
2016-12-31 550 470.356433 79.643567
2017-03-31 350 369.763731 -19.763731
2017-06-30 250 267.520184 -17.520184
2017-09-30 550 447.568015 102.431985
2017-12-31 550 651.762938 -101.762938
2018-03-31 400 385.451892 14.548108
2018-06-30 350 301.071964 48.928036
2018-09-30 600 604.816435 -4.816435
2018-12-31 750 727.515926 22.484074
2019-03-31 500 507.832724 -7.832724
2019-06-30 400 376.886213 23.113787
2019-09-30 650 696.642013 -46.642013
2019-12-31 850 793.197655 56.802345

Mallin hyvyys

Mallin hyvyyden tarkasteluun on monia tapoja. Tässä käytän

  • ennustevirheiden neliöiden keskiarvoa (MSE = mean squared error)
  • ennustevirheiden itseisarvojen keskiarvoa (MAD = mean absolute deviation)
  • ennustevirheiden aikasarjaa viivakaaviona
  • ennusteiden ja toteutuneiden arvojen hajontakaaviota
In [10]:
from sklearn.metrics import mean_squared_error, mean_absolute_error

print('mean squared error', mean_squared_error(df['Kysyntä'], df['Ennuste']))
print('mean absolute error', mean_absolute_error(df['Kysyntä'], df['Ennuste']))
mean squared error 2848.6320923500507
mean absolute error 41.853462020342796
In [11]:
# Ennustevirheet aikasarjana
# On hyvä, jos ennustevirheiden aikasarjan vaihtelu on sattumanvaraista
df['Ennustevirhe'].plot()
plt.ylabel('Ennustevirhe')
Out[11]:
Text(0, 0.5, 'Ennustevirhe')
In [12]:
# Ennusteiden ja toteutuneiden kysytöjen hajontakaavio
# Ennustemalli on sitä parempi, mitä paremmin pisteet seuraavat suoraa viivaa
# vasemmasta alakulmasta oikeaan yläkulmaan
plt.scatter(x = df['Ennuste'], y = df['Kysyntä'])
plt.xlabel('Ennuste')
plt.ylabel('Toteutunut kysyntä')
Out[12]:
Text(0, 0.5, 'Toteutunut kysyntä')

Ennusteiden laskeminen

Ennustettavien ajankohtien aikaleimojen määrittämiseksi:

  • ensimmäisen ennustettavan ajankohdan aikaleima
  • ennustettavien ajankohtien lukumäärä (periods)
  • ennustettavien ajankohtien frekvenssi (freq)

Lisätietoa freq-parametrin mahdollisista arvoista https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases

In [13]:
# Ennustettavien ajankohtien aikaleimat (alkupäivänä ensimmäisen ennusteen aikaleima)
index = pd.date_range('2020-03-31', periods = 4, freq = 'Q')

# Ennusteet neljälle vuosineljännekselle
ennusteet = malli.forecast(4)

# Ennusteet dataframeen
df_ennuste = pd.DataFrame(data = ennusteet, index = index, 
                          columns = ['Ennuste'])
df_ennuste
Out[13]:
Ennuste
2020-03-31 571.427756
2020-06-30 428.758758
2020-09-30 751.306153
2020-12-31 906.051259
In [14]:
# Viivakaavio havainnoista
df['Kysyntä'].plot()

# Ennusteet kaavioon
df_ennuste['Ennuste'].plot()
Out[14]:
<matplotlib.axes._subplots.AxesSubplot at 0x2428ac960b8>
In [15]:
# Ennusteet dataframeen alkuperäisen aikasarjan perään
df = pd.concat([df, df_ennuste])
df
Out[15]:
Kysyntä Ennuste Ennustevirhe
2013-12-31 500.0 500.342943 -0.342943
2014-03-31 350.0 343.399570 6.600430
2014-06-30 250.0 264.996683 -14.996683
2014-09-30 400.0 447.003743 -47.003743
2014-12-31 450.0 497.038058 -47.038058
2015-03-31 350.0 314.156346 35.843654
2015-06-30 200.0 262.197606 -62.197606
2015-09-30 300.0 371.504841 -71.504841
2015-12-31 350.0 382.323669 -32.323669
2016-03-31 200.0 246.320936 -46.320936
2016-06-30 150.0 159.554132 -9.554132
2016-09-30 400.0 273.680070 126.319930
2016-12-31 550.0 470.356433 79.643567
2017-03-31 350.0 369.763731 -19.763731
2017-06-30 250.0 267.520184 -17.520184
2017-09-30 550.0 447.568015 102.431985
2017-12-31 550.0 651.762938 -101.762938
2018-03-31 400.0 385.451892 14.548108
2018-06-30 350.0 301.071964 48.928036
2018-09-30 600.0 604.816435 -4.816435
2018-12-31 750.0 727.515926 22.484074
2019-03-31 500.0 507.832724 -7.832724
2019-06-30 400.0 376.886213 23.113787
2019-09-30 650.0 696.642013 -46.642013
2019-12-31 850.0 793.197655 56.802345
2020-03-31 NaN 571.427756 NaN
2020-06-30 NaN 428.758758 NaN
2020-09-30 NaN 751.306153 NaN
2020-12-31 NaN 906.051259 NaN

Mallin statistiikkaa

malli-oliosta löytyy monenlaista statistiikkaa. Mallin parametrit on laskettu siten että mean squared error saadaan mahdollisimman pieneksi.

In [16]:
malli.params
Out[16]:
{'smoothing_level': 0.8700767334936588,
 'smoothing_slope': 1.1008614433032791e-21,
 'smoothing_seasonal': 0.0,
 'damping_slope': nan,
 'initial_level': 571.4279827113896,
 'initial_slope': 18.70354865082524,
 'initial_seasons': array([0.84784987, 0.56435346, 0.4157706 , 0.71556898]),
 'use_boxcox': False,
 'lamda': None,
 'remove_bias': False}