Data yang digunakan adalah harga tutup harian komoditas emas dalam USD. Data terbagi menjadi training set (1 Januari 2001 - 31 Desember 2019 [4748 hari]) dan testing set (1 Januari 2020 - 21 Januari 2022 [752 hari]).
Data akan dieksplorasi dan didekomposisi untuk dilihat pergerakan tren dan musimannya. Kemudian, data-data yang hilang nilainya akan diimputasi menggunakan metode interpolasi. Metode peramalan deret waktu menggunakan SES, DES, SMA, dan DMA pada dataset untuk dicari parameter-parameter terbaiknya. Pemilihan model terbaik akan didasarkan pada metrik MSE dan MAE-nya.
Penyesuaian format data dilakukan dengan cara mengubah tipe data kolom X pada df menjadi datetime. Kemudian, dibuatlah objek time-series dari data tanggal dan harga tutup pada df. Lalu, data hilang diimputasi menggunakan interpolasi.
df <- read.csv("GOLD.csv")
df$X <- as.Date(df$X)
str(df)
## 'data.frame': 5449 obs. of 7 variables:
## $ X : Date, format: "2000-08-30" "2000-08-31" ...
## $ GC.F.Open : num 274 275 277 NA 276 ...
## $ GC.F.High : num 274 278 277 NA 276 ...
## $ GC.F.Low : num 274 275 277 NA 276 ...
## $ GC.F.Close : num 274 278 277 NA 276 ...
## $ GC.F.Volume : int 0 0 0 NA 2 0 125 0 0 0 ...
## $ GC.F.Adjusted: num 274 278 277 NA 276 ...
library(xts)
## Warning: package 'xts' was built under R version 4.1.2
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(tsbox)
## Warning: package 'tsbox' was built under R version 4.1.2
ts.data <- ts_ts(xts(df$GC.F.Close, df$X))
class(ts.data)
## [1] "ts"
sum(is.na(ts.data))
## [1] 2447
library(imputeTS)
## Warning: package 'imputeTS' was built under R version 4.1.2
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
##
## Attaching package: 'imputeTS'
## The following object is masked from 'package:zoo':
##
## na.locf
ts.data.imp <- na_interpolation(ts.data)
plot(ts.data.imp, main = "Gold Closing Price", col = "black")
Tidak seluruh data akan relevan untuk memprediksi waktu di masa yang akan datang, sehingga tidak seluruh data pula akan digunakan. Data yang digunakan untuk pemodelan akan dimulai dari minimal 15 tahun terakhir (1 Januari 2007). Terakhir, data dibagi menjadi data latih dan data uji. Pembagian data adalah latih 86% (Jan 2007 - Des 2019) dan uji 14% (Jan 2020 - 21 Jan 2022).
ts.data.sel <- window(ts.data.imp, start = 2007)
plot(ts.data.sel, main = "Selected Gold Closing Price", ylab = "Price (US$)")
ts.data.train <- window(ts.data.sel, end = 2020)
ts.data.test <- window(ts.data.sel, start = 2020)
start(ts.data.train); end(ts.data.train)
## [1] 2007.001
## [1] 2019.998
start(ts.data.test); end(ts.data.test)
## [1] 2020
## [1] 2022.057
length(ts.data.train) # 4748
## [1] 4748
h <- length(ts.data.test); h # 752
## [1] 752
Data uji cenderung merupakan kelanjutan dari pola data latih, sehingga pemilihan rentang waktu sudah cukup baik.
plot.train <- ts.data.sel
plot.train[time(ts.data.sel) >= start(ts.data.test)] <- NA
plot.test <- ts.data.test
plot.zoo(cbind(plot.train, plot.test), main = "Train-Test Split",
plot.type = "single", col = c("black", "blue"), ylab = "Price (US$)")
legend("bottomright", legend = c("train", "test"), col = c("black", "blue"),
lty = 1)
Hasil dekomposisi menunjukkan bahwa data mengandung unsur musiman yang berulang setiap tahunnya. Hal tersebut menunjukkan bahwa data memiliki siklus tahunan.
plot(decompose(ts.data.train))
library(forecast)
## Warning: package 'forecast' was built under R version 4.1.2
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(Metrics)
## Warning: package 'Metrics' was built under R version 4.1.2
##
## Attaching package: 'Metrics'
## The following object is masked from 'package:forecast':
##
## accuracy
library(TTR)
## Warning: package 'TTR' was built under R version 4.1.2
Fungsi myTS digunakan untuk mengubah data bertipe vektor menjadi objek ts dengan satuan penanggalan yang menyesuaikan dengan data uji. Sementara itu, fungsi myMetrics digunakan untuk secara otomatis mengeluarkan metrik MSE dan MAE untuk hasil-hasil prediksi.
myTS <- function (x) {
return(ts(x, start = start(ts.data.test), end = end(ts.data.test),
frequency = frequency(ts.data.test)))
}
myMetrics <- function (yTrue, yPred) {
colName <- deparse(substitute(yPred))
yTrue <- as.vector(yTrue)
yPred <- as.vector(yPred)
result <- as.data.frame(c(mse(yTrue, yPred), mae(yTrue, yPred)))
colnames(result) <- colName
rownames(result) <- c("MSE", "MAE")
return(result)
}
Parameter dari SMA adalah n. Parameter yang akan divalidasi adalah 4, 8, 12, 16, dan 20. Hasil uji menunjukkan bahwa metrik terbaik dimiliki oleh SMA(12).
sma4 <- forecast(SMA(ts.data.train, n = 4), h = h)$mean
## Warning in ets(object, lambda = lambda, biasadj = biasadj,
## allow.multiplicative.trend = allow.multiplicative.trend, : Missing values
## encountered. Using longest contiguous portion of time series
sma8 <- forecast(SMA(ts.data.train, n = 8), h = h)$mean
## Warning in ets(object, lambda = lambda, biasadj = biasadj,
## allow.multiplicative.trend = allow.multiplicative.trend, : Missing values
## encountered. Using longest contiguous portion of time series
sma12 <- forecast(SMA(ts.data.train, n = 12), h = h)$mean
## Warning in ets(object, lambda = lambda, biasadj = biasadj,
## allow.multiplicative.trend = allow.multiplicative.trend, : Missing values
## encountered. Using longest contiguous portion of time series
sma16 <- forecast(SMA(ts.data.train, n = 16), h = h)$mean
## Warning in ets(object, lambda = lambda, biasadj = biasadj,
## allow.multiplicative.trend = allow.multiplicative.trend, : Missing values
## encountered. Using longest contiguous portion of time series
sma20 <- forecast(SMA(ts.data.train, n = 20), h = h)$mean
## Warning in ets(object, lambda = lambda, biasadj = biasadj,
## allow.multiplicative.trend = allow.multiplicative.trend, : Missing values
## encountered. Using longest contiguous portion of time series
myMetrics(ts.data.test, sma4)
## sma4
## MSE 80819.461
## MAE 265.055
myMetrics(ts.data.test, sma8)
## sma8
## MSE 68083.104
## MAE 240.697
myMetrics(ts.data.test, sma12) # best
## sma12
## MSE 65130.2898
## MAE 235.2418
myMetrics(ts.data.test, sma16)
## sma16
## MSE 72150.4135
## MAE 249.3069
myMetrics(ts.data.test, sma20)
## sma20
## MSE 682481.9213
## MAE 650.3323
myDMA <- function (data, m) {
result <- list()
smooth.sma <- SMA(data, n = m) # SMA
smooth.dma <- SMA(smooth.sma, n = m) # DMA = SMA of SMA
# DMA forecast formula
At <- 2*smooth.sma - smooth.dma
Bt <- 2/(m - 1)*(smooth.sma - smooth.dma)
data.dma <- At + Bt
forecast.dma <- c(NA, smooth.dma)
bind <- cbind(data = c(data, NA),
smooth.dma = c(smooth.dma, NA),
forecast = forecast.dma)
f = At[length(At)] + Bt[length(Bt)]*(1:h)
future <- data.frame(data = rep(NA, h),
smooth.dma = rep(NA, h),
forecast = f)
return(ts(future$forecast, start = start(ts.data.test),
end = end(ts.data.test), frequency = frequency(ts.data.test)))
}
Fungsi myDMA dibuat untuk menggabungkan dua fungsi SMA yang berbeda dan menghasilkan pemodelan DMA yang sesuai. Parameter dari DMA adalah m. Parameter yang akan divalidasi adalah 7, 30, 90, 120, dan 365. Hasil uji menunjukkan bahwa metrik terbaik dimiliki oleh DMA(365).
dma7 <- myDMA(ts.data.train, m = 7)
dma30 <- myDMA(ts.data.train, m = 30)
dma90 <- myDMA(ts.data.train, m = 90)
dma120 <- myDMA(ts.data.train, m = 120)
dma365 <- myDMA(ts.data.train, m = 365)
myMetrics(ts.data.test, dma7)
## dma7
## MSE 2725720.065
## MAE 1377.857
myMetrics(ts.data.test, dma30)
## dma30
## MSE 39254.3904
## MAE 166.1042
myMetrics(ts.data.test, dma90)
## dma90
## MSE 170149.3060
## MAE 391.9683
myMetrics(ts.data.test, dma120)
## dma120
## MSE 36209.5287
## MAE 162.2277
myMetrics(ts.data.test, dma365) # best
## dma365
## MSE 23989.6959
## MAE 121.0686
plot(plot.train, ylim = c(min(plot.train, na.rm = T), max(plot.test)),
ylab = "Price (US$)", main = "Best SMA vs Best DMA")
lines(plot.test, col = "blue")
lines(myTS(sma12), col = "purple", lwd = 3, lty = "dotted")
lines(dma365, col = "red", lwd = 3, lty = "dotted")
legend("bottomright", legend = c("SMA (12)", "DMA (365)"),
col = c("purple", "red"), lty = "dotted", lwd = 3)
Parameter dari SES adalah alpha. Parameter yang akan divalidasi adalah 0.2, 0.4, 0.6, 0.8, dan optimum. Alpha optimum pada kasus ini adalah 0.99. Hasil uji menunjukkan bahwa metrik terbaik dimiliki oleh SES(alpha = 0.99).
ses20 <- ses(ts.data.train, h = h, alpha = .20)$mean
ses40 <- ses(ts.data.train, h = h, alpha = .40)$mean
ses60 <- ses(ts.data.train, h = h, alpha = .60)$mean
ses80 <- ses(ts.data.train, h = h, alpha = .80)$mean
sesOpt <- ses(ts.data.train, h = h, alpha = NULL)
paste("alpha optimum: ", sesOpt$model$par[['alpha']])
## [1] "alpha optimum: 0.999899988982947"
sesOpt <- sesOpt$mean
myMetrics(ts.data.test, ses20) # alpha .2
## ses20
## MSE 88286.5546
## MAE 278.5275
myMetrics(ts.data.test, ses40) # alpha .4
## ses40
## MSE 84026.5034
## MAE 270.8966
myMetrics(ts.data.test, ses60) # alpha .6
## ses60
## MSE 82906.6152
## MAE 268.8613
myMetrics(ts.data.test, ses80) # alpha .8
## ses80
## MSE 82300.2877
## MAE 267.7533
myMetrics(ts.data.test, sesOpt) # alpha .99 # best
## sesOpt
## MSE 81759.9137
## MAE 266.7619
Parameter dari DES adalah alpha dan beta. Parameter yang akan divalidasi adalah kombinasi dari: alpha (0.25, 0.5, dan 0.75) dan beta (0.25, 0.5, dan 0.75). Alpha dan beta optimum pada kasus ini masing-masing adalah 1 dan 0. Hasil uji menunjukkan bahwa metrik terbaik dimiliki oleh DES(alpha = 0.5, beta = 0.75).
des2525 <- forecast(HoltWinters(ts.data.train, alpha = .25,
beta = .25, gamma = FALSE), h = h)$mean
des2550 <- forecast(HoltWinters(ts.data.train, alpha = .25,
beta = .50, gamma = FALSE), h = h)$mean
des2575 <- forecast(HoltWinters(ts.data.train, alpha = .25,
beta = .75, gamma = FALSE), h = h)$mean
des5025 <- forecast(HoltWinters(ts.data.train, alpha = .50,
beta = .25, gamma = FALSE), h = h)$mean
des5050 <- forecast(HoltWinters(ts.data.train, alpha = .50,
beta = .50, gamma = FALSE), h = h)$mean
des5075 <- forecast(HoltWinters(ts.data.train, alpha = .50,
beta = .75, gamma = FALSE), h = h)$mean
des7525 <- forecast(HoltWinters(ts.data.train, alpha = .75,
beta = .25, gamma = FALSE), h = h)$mean
des7550 <- forecast(HoltWinters(ts.data.train, alpha = .75,
beta = .50, gamma = FALSE), h = h)$mean
des7575 <- forecast(HoltWinters(ts.data.train, alpha = .75,
beta = .50, gamma = FALSE), h = h)$mean
desOpt <- HoltWinters(ts.data.train, alpha = NULL, beta = NULL, gamma = FALSE)
paste("alpha optimum:", desOpt$alpha)
## [1] "alpha optimum: 1"
paste("beta optimum:", desOpt$beta)
## [1] "beta optimum: 0"
desOpt <- forecast(desOpt, h = h)$mean
myMetrics(ts.data.test, des2525) # alpha = .25; beta = .25
## des2525
## MSE 2483712.141
## MAE 1311.799
myMetrics(ts.data.test, des2550) # alpha = .25; beta = .50
## des2550
## MSE 1588844.254
## MAE 1038.109
myMetrics(ts.data.test, des2575) # alpha = .25; beta = .75
## des2575
## MSE 60121.5424
## MAE 191.0497
myMetrics(ts.data.test, des5025) # alpha = .50; beta = .25
## des5025
## MSE 1113194.8199
## MAE 857.5382
myMetrics(ts.data.test, des5050) # alpha = .50; beta = .50
## des5050
## MSE 76980.8417
## MAE 213.1952
myMetrics(ts.data.test, des5075) # alpha = .50; beta = .75 # best
## des5075
## MSE 23858.5390
## MAE 117.7018
myMetrics(ts.data.test, des7525) # alpha = .75; beta = .25
## des7525
## MSE 910520.0957
## MAE 769.0104
myMetrics(ts.data.test, des7550) # alpha = .75; beta = .50
## des7550
## MSE 423558.1539
## MAE 504.0681
myMetrics(ts.data.test, des7575) # alpha = .75; beta = .75
## des7575
## MSE 423558.1539
## MAE 504.0681
myMetrics(ts.data.test, desOpt) # alpha = 1; beta = 0
## desOpt
## MSE 81759.6475
## MAE 266.7615
plot(plot.train, ylim = c(min(plot.train, na.rm = T), max(plot.test)),
ylab = "Price (US$)", main = "Best SES vs Best DES")
lines(plot.test, col = "blue")
lines(sesOpt, col = "purple", lwd = 3, lty = "dotted")
lines(des5075, col = "red", lwd = 3, lty = "dotted")
legend("bottomright", legend = c("SES (.99)", "DES (.50; .75)"), col = c("purple", "red"),
lty = "dotted", lwd = 3)
Walaupun pada eksplorasi data telah ditunjukkan bahwa data memiliki siklus musiman, fungsi HoltWinters tidak dapat digunakan.
try(addOpt <- HoltWinters(ts.data.train, seasonal = "additive"))
## Error in decompose(ts(x[1L:wind], start = start(x), frequency = f), seasonal) :
## time series has no or less than 2 periods
try(mulOpt <- HoltWinters(ts.data.train, seasonal = "multiplicative"))
## Error in decompose(ts(x[1L:wind], start = start(x), frequency = f), seasonal) :
## time series has no or less than 2 periods
Hasil uji metrik menunjukkan bahwa DES(.5, .75) adalah model terbaik. Akan tetapi, model DES(365) juga dapat dipilih sebagai alternatif karena metriknya tidak berbeda dan pendekatannya jauh lebih sederhana. Plot hasil prediksi ditunjukkan pada grafik di bawah.
myMetrics(ts.data.test, sma12)
## sma12
## MSE 65130.2898
## MAE 235.2418
myMetrics(ts.data.test, dma365)
## dma365
## MSE 23989.6959
## MAE 121.0686
myMetrics(ts.data.test, sesOpt)
## sesOpt
## MSE 81759.9137
## MAE 266.7619
myMetrics(ts.data.test, des5075) # best
## des5075
## MSE 23858.5390
## MAE 117.7018
plot(ts.data.test, ylim = c(min(plot.test), max(plot.test)),
main = "Gold Price", ylab = "Price (US$)")
lines(myTS(sma12), col = "purple", lty = "dashed", lwd = 2)
lines(dma365, col = "red", lty = "dashed", lwd = 2)
lines(sesOpt, col = "orange", lty = "dashed", lwd = 2)
lines(des5075, col = "blue", lty = "dashed", lwd = 2)
legend("topleft", legend = c("Test Data", "SMA", "DMA", "SES", "DES"),
col = c("black", "purple", "red", "orange", "blue"),
lty = c(1, 2, 2, 2, 2), cex = .75)