Yksinkertainen eksponentiaalinen tasoitus

Yksinkertainen eksponentiaalinen tasoitus ei huomioi trendiä eikä kausivaihtelua.

Jos ennustetaan pidemmälle kuin seuraavaan aikaleimaan, niin yksinkertainen eksoponentiaalinen tasoitus antaa kaikille tuleville aikaleimoille saman ennusteen.

Eksponentiaalinen tasoitus löytyy statsmodels.tsa-kirjastosta.

Mallin hyvyyttä kuvaavia tunnuslukuja löytyy sklearn.metrics-kirjastosta.

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

Tässä esimerkissä yksinkertainen eksoponentiaalinen tasoitus ei ole hyvä malli, koska aikasarjassa on selkeä trendi ja kausivaihtelu, jotka malli jättää huomiotta!

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

# freq='Q': aikaleimat vuosineljänneksen viimeisiä päiviä
malli = ExponentialSmoothing(df['Kysyntä'], freq='Q').fit()
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)
df['Ennuste'] = malli.fittedvalues
df
Out[7]:
Kysyntä Ennuste
Vuosineljännes
2013-12-31 500 388.104129
2014-03-31 350 424.218639
2014-06-30 250 400.264495
2014-09-30 400 351.766472
2014-12-31 450 367.333894
2015-03-31 350 394.014466
2015-06-30 200 379.808751
2015-09-30 300 321.775288
2015-12-31 350 314.747291
2016-03-31 200 326.125140
2016-06-30 150 285.418119
2016-09-30 400 241.711779
2016-12-31 550 292.799468
2017-03-31 350 375.811209
2017-06-30 250 367.480614
2017-09-30 550 329.563623
2017-12-31 550 400.709695
2018-03-31 400 448.893297
2018-06-30 350 433.112934
2018-09-30 600 406.288148
2018-12-31 750 468.808851
2019-03-31 500 559.563589
2019-06-30 400 540.339378
2019-09-30 650 495.044697
2019-12-31 850 545.056683
In [8]:
# Alkuperäinen aikasarja ja mallin mukaiset ennusteet samaan kaavioon
df.plot()
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x15e25ec9438>
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 388.104129 111.895871
2014-03-31 350 424.218639 -74.218639
2014-06-30 250 400.264495 -150.264495
2014-09-30 400 351.766472 48.233528
2014-12-31 450 367.333894 82.666106
2015-03-31 350 394.014466 -44.014466
2015-06-30 200 379.808751 -179.808751
2015-09-30 300 321.775288 -21.775288
2015-12-31 350 314.747291 35.252709
2016-03-31 200 326.125140 -126.125140
2016-06-30 150 285.418119 -135.418119
2016-09-30 400 241.711779 158.288221
2016-12-31 550 292.799468 257.200532
2017-03-31 350 375.811209 -25.811209
2017-06-30 250 367.480614 -117.480614
2017-09-30 550 329.563623 220.436377
2017-12-31 550 400.709695 149.290305
2018-03-31 400 448.893297 -48.893297
2018-06-30 350 433.112934 -83.112934
2018-09-30 600 406.288148 193.711852
2018-12-31 750 468.808851 281.191149
2019-03-31 500 559.563589 -59.563589
2019-06-30 400 540.339378 -140.339378
2019-09-30 650 495.044697 154.955303
2019-12-31 850 545.056683 304.943317

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 22507.609441534438
mean absolute error 128.19564759212219
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 643.477458
2020-06-30 643.477458
2020-09-30 643.477458
2020-12-31 643.477458
In [14]:
# Viivakaavio havainnoista
df['Kysyntä'].plot()

# Ennusteet kaavioon
df_ennuste['Ennuste'].plot()
Out[14]:
<matplotlib.axes._subplots.AxesSubplot at 0x15e2638a128>
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 388.104129 111.895871
2014-03-31 350.0 424.218639 -74.218639
2014-06-30 250.0 400.264495 -150.264495
2014-09-30 400.0 351.766472 48.233528
2014-12-31 450.0 367.333894 82.666106
2015-03-31 350.0 394.014466 -44.014466
2015-06-30 200.0 379.808751 -179.808751
2015-09-30 300.0 321.775288 -21.775288
2015-12-31 350.0 314.747291 35.252709
2016-03-31 200.0 326.125140 -126.125140
2016-06-30 150.0 285.418119 -135.418119
2016-09-30 400.0 241.711779 158.288221
2016-12-31 550.0 292.799468 257.200532
2017-03-31 350.0 375.811209 -25.811209
2017-06-30 250.0 367.480614 -117.480614
2017-09-30 550.0 329.563623 220.436377
2017-12-31 550.0 400.709695 149.290305
2018-03-31 400.0 448.893297 -48.893297
2018-06-30 350.0 433.112934 -83.112934
2018-09-30 600.0 406.288148 193.711852
2018-12-31 750.0 468.808851 281.191149
2019-03-31 500.0 559.563589 -59.563589
2019-06-30 400.0 540.339378 -140.339378
2019-09-30 650.0 495.044697 154.955303
2019-12-31 850.0 545.056683 304.943317
2020-03-31 NaN 643.477458 NaN
2020-06-30 NaN 643.477458 NaN
2020-09-30 NaN 643.477458 NaN
2020-12-31 NaN 643.477458 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.3227510470262608,
 'smoothing_slope': nan,
 'smoothing_seasonal': nan,
 'damping_slope': nan,
 'initial_level': 388.10412909955073,
 'initial_slope': nan,
 'initial_seasons': array([], dtype=float64),
 'use_boxcox': False,
 'lamda': None,
 'remove_bias': False}