Kaksinkertainen eksponentiaalinen tasoitus

Kaksinkertaista eksponentiaalista tasoitusta kutsutaan myös Holtin malliksi.

Holtin malli huomioi trendin, mutta ei huomioi kausivaihtelua.

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

Tässä esimerkissä kaksinkertainen eksoponentiaalinen tasoitus ei ole hyvä malli, koska aikasarjassa on selkeä kausivaihtelu, jonka 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 0x1b6bda61940>
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 huomioidaan (trend='add')
# freq='Q': aikaleimat vuosineljänneksen viimeisiä päiviä
malli = ExponentialSmoothing(df['Kysyntä'], trend = 'add', 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
C:\Users\taaak\AppData\Local\Continuum\anaconda3\lib\site-packages\statsmodels\tsa\holtwinters.py:744: ConvergenceWarning: Optimization failed to converge. Check mle_retvals.
  ConvergenceWarning)
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 409.375106
2014-03-31 350 437.994264
2014-06-30 250 415.978939
2014-09-30 400 363.746160
2014-12-31 450 364.812391
2015-03-31 350 383.633888
2015-06-30 200 370.355493
2015-09-30 300 311.770590
2015-12-31 350 292.425198
2016-03-31 200 294.226281
2016-06-30 150 251.758938
2016-09-30 400 200.922172
2016-12-31 550 238.600525
2017-03-31 350 324.400674
2017-06-30 250 339.762662
2017-09-30 550 320.328315
2017-12-31 550 396.043768
2018-03-31 400 462.456057
2018-06-30 350 470.327921
2018-09-30 600 455.956448
2018-12-31 750 517.406019
2019-03-31 500 615.972759
2019-06-30 400 619.273030
2019-09-30 650 582.583862
2019-12-31 850 622.471128
In [8]:
# Alkuperäinen aikasarja ja mallin mukaiset ennusteet samaan kaavioon
df.plot()
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x1b6c016d518>
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 409.375106 90.624894
2014-03-31 350 437.994264 -87.994264
2014-06-30 250 415.978939 -165.978939
2014-09-30 400 363.746160 36.253840
2014-12-31 450 364.812391 85.187609
2015-03-31 350 383.633888 -33.633888
2015-06-30 200 370.355493 -170.355493
2015-09-30 300 311.770590 -11.770590
2015-12-31 350 292.425198 57.574802
2016-03-31 200 294.226281 -94.226281
2016-06-30 150 251.758938 -101.758938
2016-09-30 400 200.922172 199.077828
2016-12-31 550 238.600525 311.399475
2017-03-31 350 324.400674 25.599326
2017-06-30 250 339.762662 -89.762662
2017-09-30 550 320.328315 229.671685
2017-12-31 550 396.043768 153.956232
2018-03-31 400 462.456057 -62.456057
2018-06-30 350 470.327921 -120.327921
2018-09-30 600 455.956448 144.043552
2018-12-31 750 517.406019 232.593981
2019-03-31 500 615.972759 -115.972759
2019-06-30 400 619.273030 -219.273030
2019-09-30 650 582.583862 67.416138
2019-12-31 850 622.471128 227.528872

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 21510.15477760745
mean absolute error 125.3775623523341
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 717.198277
2020-06-30 754.552979
2020-09-30 791.907682
2020-12-31 829.262385
In [14]:
# Viivakaavio havainnoista
df['Kysyntä'].plot()

# Ennusteet kaavioon
df_ennuste['Ennuste'].plot()
Out[14]:
<matplotlib.axes._subplots.AxesSubplot at 0x1b6c063a0f0>
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 409.375106 90.624894
2014-03-31 350.0 437.994264 -87.994264
2014-06-30 250.0 415.978939 -165.978939
2014-09-30 400.0 363.746160 36.253840
2014-12-31 450.0 364.812391 85.187609
2015-03-31 350.0 383.633888 -33.633888
2015-06-30 200.0 370.355493 -170.355493
2015-09-30 300.0 311.770590 -11.770590
2015-12-31 350.0 292.425198 57.574802
2016-03-31 200.0 294.226281 -94.226281
2016-06-30 150.0 251.758938 -101.758938
2016-09-30 400.0 200.922172 199.077828
2016-12-31 550.0 238.600525 311.399475
2017-03-31 350.0 324.400674 25.599326
2017-06-30 250.0 339.762662 -89.762662
2017-09-30 550.0 320.328315 229.671685
2017-12-31 550.0 396.043768 153.956232
2018-03-31 400.0 462.456057 -62.456057
2018-06-30 350.0 470.327921 -120.327921
2018-09-30 600.0 455.956448 144.043552
2018-12-31 750.0 517.406019 232.593981
2019-03-31 500.0 615.972759 -115.972759
2019-06-30 400.0 619.273030 -219.273030
2019-09-30 650.0 582.583862 67.416138
2019-12-31 850.0 622.471128 227.528872
2020-03-31 NaN 717.198277 NaN
2020-06-30 NaN 754.552979 NaN
2020-09-30 NaN 791.907682 NaN
2020-12-31 NaN 829.262385 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.25215457578395606,
 'smoothing_slope': 0.25215457021177223,
 'smoothing_seasonal': nan,
 'damping_slope': nan,
 'initial_level': 409.36953535887795,
 'initial_slope': 0.005570608536064859,
 'initial_seasons': array([], dtype=float64),
 'use_boxcox': False,
 'lamda': None,
 'remove_bias': False}