Wczytanie danych z pliku. W danych zawarty jest opis, dlatego pierwsze 15 wierszy zostało pominiete.
library(readxl)
library(ggplot2)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(forecast)
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(zoo)
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
df = read_excel('Dow_Jones.xlsx', skip=15, col_names = c("month", "index_value"))
df
Podstawowe statystyki:
summary(df)
## month index_value
## Min. :1968-08-01 00:00:00.00 Min. : 0
## 1st Qu.:1974-08-01 00:00:00.00 1st Qu.: 3672
## Median :1980-08-01 00:00:00.00 Median : 3754
## Mean :1980-08-26 05:33:50.44 Mean : 4133
## 3rd Qu.:1986-10-01 00:00:00.00 3rd Qu.: 3850
## Max. :1992-10-01 00:00:00.00 Max. :120000
## NA's :2 NA's :2
str(df)
## tibble [291 × 2] (S3: tbl_df/tbl/data.frame)
## $ month : POSIXct[1:291], format: "1968-08-01" "1968-09-01" ...
## $ index_value: num [1:291] 3645 3626 3634 3620 3607 ...
nrow(df)
## [1] 291
Po przeglądnieciu statysyk można zauważyc, że mamy tutaj do czynienia z szeregiem czasowym określającym wartość indeksu Dow-Jones na koniec każdego miesiąca. W data framie występują dwie wartości brakujące. Zobaczmy jak wygląda przebieg tego szeregu.
ggplot(data=df, aes(x=month, y=index_value)) + geom_line()
## Warning: Removed 2 row(s) containing missing values (geom_path).
ggplot(data=df,aes(y=index_value)) + geom_boxplot()
## Warning: Removed 2 rows containing non-finite values (stat_boxplot).
ggplot(data=df, aes(x=index_value)) + geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 2 rows containing non-finite values (stat_bin).
Na każdym z trzech powyżej przedstawionych wykresów można zauważyć,
występujące wartości odstające w pobliżu 120000 oraz 0, które zabużają
wartość indeksu. Wartości te są abstrakcyjne, ponieważ w historii indeks
nigdy nie osiągnął 0 ani 120000. W następnych krokach szereg zostanie
oczyszczony z wartości odstających metodą wskaźnika IQR
Q1 = quantile(df$index_value, .25, na.rm=TRUE)
Q3 = quantile(df$index_value, .75, na.rm=TRUE)
IQR = IQR(df$index_value, na.rm=TRUE)
df = df %>%
mutate(index_value = ifelse(df$index_value > (Q1 - 1.5*IQR) &
df$index_value < (Q3 + 1.5*IQR), index_value, NA)
)
sum(is.na(df$index_value))
## [1] 5
Rozkład indeksu nie należy do rozkładu normalnego
ggplot(data=df, aes(x=index_value)) + geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 5 rows containing non-finite values (stat_bin).
ggplot(data=df,aes(y=index_value)) + geom_boxplot()
## Warning: Removed 5 rows containing non-finite values (stat_boxplot).
Wartości odstające zostały usunięte, aczkolwiek w zbiorze nadal
występują braki danych co widać też na wykresie poniżej.
ggplot(data=df, aes(x=month, y=index_value)) + geom_line()
## Warning: Removed 2 row(s) containing missing values (geom_path).
Ponieważ jest to szereg czasowy i są to dane historyczny pozbycie sie 5
miesięcy ze zbioru może wiąząć się z niepoprawnymi wynikami w predykcji,
tego nie chcemy, dlatego wartości odstające zostaną usunięte.Do
zastąpienie brakujących wartości uzyjemy funkcji aproksymacyjnej z
pakietu zoo.
i_ts = ts(df$index_value,start=c(1968,8),end=c(1992,10),frequency=12)
i_ts = na.approx(i_ts);
sum(is.na(i_ts))
## [1] 0
autoplot(i_ts)
Z wykresu liniowego możemy wysunąć stwierdzenie, że występuje trend
rosnący z pewną powtażającą się sezonowością addytywną. Zobaczmy wykresy
ACF oraz PACF potwierdzenia hipotezy.
acf(df$index_value, na.action = na.pass)
Na wykresie ACF można zauwazyć powolny spadek wraz ze wzortem lagów,
oznacza to że zbiór posiada trend. Co do sezonowatosci ciezko tutaj
stwierdzić, ponieważ brak jest lagów ktore odbiegały by rozmiarem od
innych, aczkolwiek można zauważyc, że peaki tworzą “delikatne fale”
pacf(df$index_value, na.action = na.pass)
Na wykrecie PACF mozemy zauwzyc znaczaca korelacje na 1 lagu, która
znacząco spadana na juz na drugi logu.
Dokonajmy dekompozycji szeregu
decomposed = decompose(i_ts)
plot(decomposed)
Usunmy trend oraz sezonowość, zeby zobrazować sobie jak te dwie cechy wplywaja na nasz szereg. Mozna zauwzyc, ze po odjeciu sezonowatosci od szeregu szereg się nie zmienil, może to oznaczyc jego brak wystepowania.
tsWithoutTrend = i_ts/decomposed$trend
plot(i_ts-decomposed$trend)
plot(i_ts-decomposed$seasonal)
Sprawdzmy jak wygladaja dane na danych intervałach 1,2,5,10 lat, w celu potwierdzenia braku sezonowatosci
startYear = 1968
while (startYear < 1992) {
plot(window(i_ts, start=c(startYear,1), end=c(startYear+1,1)))
startYear =startYear+1
}
## Warning in window.default(x, ...): 'start' value not changed
startYear = 1968
while (startYear < 1992) {
plot(window(i_ts, start=c(startYear,1), end=c(startYear+2,1)))
startYear =startYear+2
}
## Warning in window.default(x, ...): 'start' value not changed
startYear = 1968
while (startYear < 1992) {
plot(window(i_ts, start=c(startYear,1), end=c(startYear+5,1)))
startYear =startYear+5
}
## Warning in window.default(x, ...): 'start' value not changed
## Warning in window.default(x, ...): 'end' value not changed
startYear = 1968
while (startYear < 1998) {
plot(window(i_ts, start=c(startYear,1), end=c(startYear+10,1)))
startYear =startYear+10
}
## Warning in window.default(x, ...): 'start' value not changed
## Warning in window.default(x, ...): 'end' value not changed
W zadnym okresie nie wystepuje podobienstwo pomiedzy wykresami, zatem
definitywnie utwierdzam się w przekonaniu, że w szeregu nie wystepuje
sezonowość. Wiec szereg ma trend rosnacy bez sezonowosci.
Przejdzmy do modelowania szeregu
Podział danych na testowe i treningowe z założeniem 4 elementowego zbioru testowego
train = head(i_ts,length(i_ts)-4)
test = tail(i_ts, 4)
is_no_trend = diff(i_ts)
train_no_trend = head(is_no_trend,length(is_no_trend)-4)
test_no_trend = tail(is_no_trend, 4)
Model nr 1. Modelowanie szeregu za pomoca metody Simple Exponential Smoothing. Metoda SES wykorzystywana jest do szeregow nie wykazujących trendu oraz sezonowosci. W przypadku naszych danych trend jest widoczny, dlatego musimy się go pozbyć
autoplot(train_no_trend)
Jako pierwszy model wykonajamy sobie model z podstawowymi
parametrami(pozwalamy modelowi wyestymowac najlepszy za nas) w oknie o
wielkosci 4
basic_model_ses = ses(train_no_trend, h=4)
basic_model_ses$model
## Simple exponential smoothing
##
## Call:
## ses(y = train_no_trend, h = 4)
##
## Smoothing parameters:
## alpha = 1e-04
##
## Initial states:
## l = 0.9572
##
## sigma: 22.4518
##
## AIC AICc BIC
## 3401.310 3401.396 3412.278
autoplot(basic_model_ses)
autoplot(train_no_trend) +
autolayer(basic_model_ses$mean,
color = "red")
autoplot(test_no_trend) +
autolayer(basic_model_ses, alpha=0.5) +
ggtitle("Predicted vs actuals")
Rozkład residuów modelu sprowadza się do rozkładu normalnego
hist(basic_model_ses$residuals)
plot(basic_model_ses$fitted, basic_model_ses$residuals)
title("fitted vs residuals")
Zoptymalizujemy parametr alpha w modelu metodą minimalizacji wskaznika RMSE w celu uzuskania lepszego przedziału rozwiązań
# comparing our model
alpha = seq(.01, .99, by = .01)
RMSE = c()
for(i in seq_along(alpha)) {
fit = ses(train_no_trend, alpha = alpha[i],
h = 4)
RMSE[i] = accuracy(fit,
test_no_trend)[2,2]
}
alpha_fit = data_frame(alpha, RMSE)
## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## ℹ Please use `tibble()` instead.
alpha_min = filter(alpha_fit,
RMSE == min(RMSE))
alpha_min
Najniższe RMSE, które i tak jest relatywnie duże zostało uzyskane zostało za pomocą alpha = 0.99
ggplot(alpha_fit, aes(alpha, RMSE)) +
geom_line() +
geom_point(data = alpha_min,
aes(alpha, RMSE),
size = 2, color = "red")
Model z parametrem alpha = 0.99
final_model_ses = ses(train_no_trend, alpha=.99, h=4)
autoplot(final_model_ses)
final_model_ses$model
## Simple exponential smoothing
##
## Call:
## ses(y = train_no_trend, h = 4, alpha = 0.99)
##
## Smoothing parameters:
## alpha = 0.99
##
## Initial states:
## l = -18.755
##
## sigma: 30.8717
##
## AIC AICc BIC
## 3581.475 3581.518 3588.787
Porównanie modeli
Przedstawienie sredniej modelu na wykresie szeregu
autoplot(train_no_trend) +
autolayer(final_model_ses$mean,
color = "red")
autoplot(test_no_trend) +
autolayer(final_model_ses, alpha=0.5) +
ggtitle("Predicted vs actuals")
Rozkład residuów modelu sprowadza się do rozkładu normalnego
hist(final_model_ses$residuals)
plot(final_model_ses$fitted, final_model_ses$residuals)
title("fitted vs residuals")
accuracy(basic_model_ses, test_no_trend)
## ME RMSE MAE MPE MAPE MASE
## Training set 1.979394e-04 22.37315 16.72553 -Inf Inf 0.7043591
## Test set -1.670725e+01 26.31197 25.22862 101.4891 101.4891 1.0624484
## ACF1 Theil's U
## Training set 0.04340117 NA
## Test set -0.11303131 0.9850212
accuracy(final_model_ses, test_no_trend)
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 0.04575685 30.76358 23.18247 NaN Inf 0.9762792 -0.5027572
## Test set -9.95059484 22.63188 21.85030 90.9787 90.9787 0.9201775 -0.1130313
## Theil's U
## Training set NA
## Test set 0.7653049
Porównianie otrzymanych modeli na wykresie
p1 <- autoplot(basic_model_ses) +
coord_cartesian(ylim = c(-120, 120)) +
ggtitle("Original SES Model")
p2 <- autoplot(final_model_ses) +
coord_cartesian(ylim = c(-120, 120)) +
ggtitle("Optimal SES Model")
gridExtra::grid.arrange(p1, p2, nrow = 1)
Dzięki optymalizacji udało się zopytamalizować RMSE z 25 do 22 oraz
wskaznik predykcji mape ze 100 do 90, co nadal nie jest zbyt zadawalącym
wynikiem. W Obu przypadkach resiuda sprowadzaja sie do rozkladu
normalnego, natomiast wariancja w modelu numer 1 jest blizsza 0 ze
wzledu na mniejszy przedział ufności możliwych wyników. Ze wzgledu na
to, że dany model oparty jest o szereg z usunietym trendem, wybrał bym
model numer 2, przede wszystkim ze wzgledu na lepszy MAPE oraz RMSE, ale
i wiekszy przedzial ufnosci, na wykresie można zuważyć można wiele
odchylen, przez co moim zdaniem taki scenariusz jest bardziej
pradopodobny.
Model nr 2. Model Holta.
Drugi model wykonany z zastosowaniem metody Holta. Wybór padł na tą metodę ze względu na występowanie trendu w szeregu, przez co idealnie pasuję do przedstawonych danych. Tak samo jak w przypadku pierwszym wykonajmy podstawowy model w oknie 4 elementowym w celu porownania wyników
holt_model_basic = holt(train, h=4)
holt_model_basic$model
## Holt's method
##
## Call:
## holt(y = train, h = 4)
##
## Smoothing parameters:
## alpha = 0.9999
## beta = 6e-04
##
## Initial states:
## l = 3625.6483
## b = 0.8272
##
## sigma: 22.5246
##
## AIC AICc BIC
## 3418.029 3418.243 3436.327
autoplot(holt_model_basic)
autoplot(test) +
autolayer(holt_model_basic, alpha= 0.5) +
ggtitle("Predicted vs actuals")
hist(holt_model_basic$residuals)
plot(holt_model_basic$fitted, holt_model_basic$residuals)
title("fitted vs residuals")
autoplot(train) +
autolayer(holt_model_basic$mean,
color = "red")
W modelu Holta jako parametr, który pozwala pozbyc się bledów z danych treningowych jest parametr beta, ktory przymuje wartosci od 0.0001 do 5
beta = seq(.0001, .5, by = .001)
RMSE = c()
for(i in seq_along(beta)) {
fit = holt(train,
beta = beta[i],
h = 4)
RMSE[i] = accuracy(fit,
test)[2,2]
}
beta_fit = data_frame(beta, RMSE)
beta_min = filter(beta_fit,
RMSE == min(RMSE))
beta_min
ggplot(beta_fit, aes(beta, RMSE)) +
geom_line() +
geom_point(data = beta_min,
aes(beta, RMSE),
size = 2, color = "red") +
ggtitle("beta vs RMSE")
final_model_holt = holt(train,
beta = 1e-04,
h = 4)
final_model_holt$model
## Holt's method
##
## Call:
## holt(y = train, h = 4, beta = 1e-04)
##
## Smoothing parameters:
## alpha = 0.9999
## beta = 1e-04
##
## Initial states:
## l = 3630.49
## b = 1.0142
##
## sigma: 22.5059
##
## AIC AICc BIC
## 3415.552 3415.693 3430.189
autoplot(test) +
autolayer(final_model_holt, alpha= 0.5) +
ggtitle("Predicted vs actuals")
hist(final_model_holt$residuals)
plot(final_model_holt$fitted, final_model_holt$residuals)
title("fitted vs residuals")
autoplot(train) +
autolayer(final_model_holt$mean,
color = "red")
Statystyki opisujące modele
accuracy(holt_model_basic, test)
## ME RMSE MAE MPE MAPE MASE
## Training set 0.1874348 22.36712 16.73841 0.003120699 0.4454027 0.2807260
## Test set -21.9043234 37.61722 30.47325 -0.567907550 0.7856141 0.5110779
## ACF1 Theil's U
## Training set 0.04082631 NA
## Test set 0.20194560 1.525088
accuracy(final_model_holt, test)
## ME RMSE MAE MPE MAPE MASE
## Training set -0.01010764 22.34851 16.71322 -0.002187622 0.4447283 0.2803034
## Test set -22.28559922 37.97747 30.77827 -0.577721842 0.7934909 0.5161934
## ACF1 Theil's U
## Training set 0.04156291 NA
## Test set 0.20226164 1.541196
p1 <- autoplot(holt_model_basic) +
coord_cartesian(ylim = c(3500, 4030)) +
ggtitle("Original Holt's model")
p2 <- autoplot(final_model_holt) +
coord_cartesian(ylim = c(3500, 4030)) +
ggtitle("Optimal Holt's model")
gridExtra::grid.arrange(p1, p2, nrow=1)
Porównąjąc model podstawowy do zopytamlizowanego, można zauwazyc, ze przedział ufnosci dla mozliwych wyników w przypadku modelu zoptymalizowanego jest mniejszy. RMSE w przypadku pierwszym jak i drugim jest na poziomnym poziomie = 37, natomiast wskaznik MAPE predykcji jest na poziomie ~ 8%, co swiadczy o dobrej predykcji dalszych wyników. W obu przypadkach rozkład residuów sprowadza się do rozkładu normalnego, oba modele znacząco się od siebie nie rożnią.
PODSUMOWANIE WYNIKÓW
Podsumowując, w modelu z wykorzystaniem SES otrzymaliśmy wniki z lepszym RMSE = 22, w przypadku modelu Holta wynosi ono ~38, natomiast wznacząco różnią się wskazniki predkycji MAPE ponieważ w pierwszym przypadku najlepszy wynik uzysakliśmy z MAPE=90, w modelu holta jest o wiele lepiej = 0.8. Podejżewam, ale nie jestem w stanie potwierdzić tej hipotezy, ze tak slaby MAPE w modelu nr 1 jest wynikiem usunięcia trendu z danych, co może wiazać się z wiekszą trudnością dalszych predykcji(w przypadku numer 2 znamy trend, co juz w pewnym stopniu ułatwia dalsze predykcje). Ze względu na brak potrzeby usuwania trendu z szeregu uważam, że nie mniej jednak algorytm Holta dał lepsze wyniki.