Data yang digunakan pada tugas ini adalah data kualitas udara harian, tepatnya variabel PM2.5 (particulate matter 2.5 mikron, ug/m3), yang diambil dari dataset Air Quality Historical. Data asli memiliki panjang 780 periode (1 Januari 2024 - 18 Februari 2026) dan dibagi rata secara berurutan (bukan diacak, agar pola deret waktu tidak rusak) ke 5 anggota kelompok, masing-masing mendapat 156 observasi.
Bagian data yang digunakan pada laporan ini adalah bagian milik Anggota 2, yaitu periode 5 Juni 2024 - 7 November 2024 (156 hari).
Tugas ini akan menerapkan dan membandingkan empat metode pemulusan yang telah dipelajari: Single Moving Average (SMA), Double Moving Average (DMA), Single Exponential Smoothing (SES), dan Double Exponential Smoothing (DES), kemudian menentukan metode terbaik berdasarkan metrik akurasi SSE, MSE, RMSE, dan MAPE pada data uji.
Package R yang digunakan pada laporan ini adalah
forecast, TTR, readxl, dan
ggplot2. Jika package tersebut belum ada, silakan install
terlebih dahulu.
# install.packages("forecast")
# install.packages("TTR")
# install.packages("readxl")
# install.packages("ggplot2")
library(forecast)
library(TTR)
library(readxl)
library(ggplot2)
Pastikan file PM25_Anggota_2.xlsx berada pada folder
kerja (working directory) yang sama dengan file Rmd ini sebelum
melakukan knit.
data <- read_excel("C:/Users/oktav/Downloads/PM25_Anggota_2.xlsx", skip = 3)
colnames(data) <- c("t", "tanggal", "pm25")
data$tanggal <- as.Date(data$tanggal, format = "%Y-%m-%d")
head(data)
## # A tibble: 6 × 3
## t tanggal pm25
## <dbl> <date> <dbl>
## 1 1 2024-06-05 104.
## 2 2 2024-06-06 87.1
## 3 3 2024-06-07 72.5
## 4 4 2024-06-08 67.8
## 5 5 2024-06-09 73.4
## 6 6 2024-06-10 83.2
tail(data)
## # A tibble: 6 × 3
## t tanggal pm25
## <dbl> <date> <dbl>
## 1 151 2024-11-02 69.8
## 2 152 2024-11-03 87.9
## 3 153 2024-11-04 89.1
## 4 154 2024-11-05 86.1
## 5 155 2024-11-06 40.8
## 6 156 2024-11-07 73.2
str(data)
## tibble [156 × 3] (S3: tbl_df/tbl/data.frame)
## $ t : num [1:156] 1 2 3 4 5 6 7 8 9 10 ...
## $ tanggal: Date[1:156], format: "2024-06-05" "2024-06-06" ...
## $ pm25 : num [1:156] 104.5 87.1 72.5 67.8 73.4 ...
dim(data)
## [1] 156 3
summary(data$pm25)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 26.48 61.80 70.16 70.43 80.40 108.38
Mengubah data menjadi objek deret waktu (ts) dengan
fungsi ts().
data.ts <- ts(data$pm25)
data.ts
## Time Series:
## Start = 1
## End = 156
## Frequency = 1
## [1] 104.4583 87.0750 72.5083 67.8042 73.4083 83.2292 66.2083 50.4083
## [9] 51.1500 52.0667 70.9083 92.8833 88.7125 83.3250 63.7708 64.5375
## [17] 74.1917 108.1250 84.3167 64.3333 61.1417 53.0625 41.9708 40.8500
## [25] 43.9292 77.3875 90.9625 64.3458 87.3583 96.3000 45.9167 26.4750
## [33] 30.7500 44.4833 46.6542 71.8542 108.3750 108.3792 101.2208 90.3750
## [41] 76.4000 66.8333 60.0833 77.6042 69.0542 70.5875 86.0417 81.1208
## [49] 88.4417 79.9042 81.2375 78.0042 64.0792 80.3583 83.6167 70.5250
## [57] 62.1750 81.5542 88.4792 62.6417 72.5417 100.5917 84.4583 83.8708
## [65] 76.8167 67.8125 69.1417 79.9250 88.5000 61.9917 58.3125 68.4250
## [73] 78.3833 51.9833 63.2875 54.8542 57.7000 56.5917 50.0958 70.1375
## [81] 94.3708 74.5458 62.3125 77.8000 77.9458 63.2708 76.6208 62.2417
## [89] 60.3917 72.9208 87.6792 72.1083 59.0458 48.4542 49.1333 53.3042
## [97] 52.0375 63.7458 46.5167 74.9625 83.3042 93.6208 88.0083 79.1583
## [105] 76.4875 58.9583 62.7750 49.5000 57.2458 71.0625 76.6875 80.5292
## [113] 73.2250 63.7208 60.9333 64.1542 76.4042 66.8083 71.3792 70.0375
## [121] 64.3875 75.0167 85.8000 65.3958 64.6708 66.7792 75.2833 70.1875
## [129] 60.1958 61.2375 57.5000 96.3000 89.7833 83.4917 75.6208 71.7708
## [137] 77.1333 42.6667 62.4708 67.2875 56.2333 64.9792 62.2750 53.8458
## [145] 79.7250 65.6042 52.4000 68.1667 66.9458 64.2833 69.7833 87.8958
## [153] 89.0917 86.1125 40.7583 73.1750
Membuat plot data deret waktu secara keseluruhan.
ts.plot(data.ts, xlab = "Periode (hari)", ylab = "PM2.5 (ug/m3)",
main = "Plot Data Deret Waktu PM2.5 - Anggota 2")
points(data.ts)
Interpretasi awal: dari plot terlihat data PM2.5 berfluktuasi di sekitar nilai rataan tertentu tanpa menunjukkan pola tren naik/turun yang tegas dalam jangka panjang, sehingga secara visual data cenderung stasioner/konstan. Pemeriksaan pola musiman mingguan (hari kerja vs akhir pekan) juga dilakukan sebagai berikut.
data$hari <- weekdays(data$tanggal)
rata_hari <- aggregate(pm25 ~ hari, data = data, FUN = mean)
rata_hari[order(rata_hari$pm25), ]
## hari pm25
## 7 Wednesday 67.59021
## 1 Friday 68.09716
## 3 Saturday 68.43011
## 5 Thursday 70.61793
## 6 Tuesday 71.43675
## 4 Sunday 72.18977
## 2 Monday 74.76666
Selisih rataan PM2.5 antar hari dalam seminggu relatif kecil dibanding rataan keseluruhan data, dan tidak membentuk pola musiman yang konsisten sehingga metode Holt-Winters (musiman) tidak digunakan pada data ini; empat metode non-musiman (SMA, DMA, SES, DES) dinilai lebih relevan untuk dibandingkan.
Pembagian data latih dan data uji dilakukan dengan perbandingan 80% data latih dan 20% data uji.
n <- nrow(data)
n_train <- floor(0.8 * n)
n_test <- n - n_train
training <- data[1:n_train, ]
testing <- data[(n_train + 1):n, ]
train.ts <- ts(training$pm25)
test.ts <- ts(testing$pm25)
n; n_train; n_test
## [1] 156
## [1] 124
## [1] 32
ggplot() +
geom_line(data = training, aes(x = t, y = pm25, col = "Data Latih")) +
geom_line(data = testing, aes(x = t, y = pm25, col = "Data Uji")) +
labs(x = "Periode (hari)", y = "PM2.5 (ug/m3)", color = "Keterangan") +
scale_colour_manual(name = "Keterangan:",
breaks = c("Data Latih", "Data Uji"),
values = c("blue", "red")) +
theme_bw() +
theme(legend.position = "bottom")
Sebelum menerapkan SMA dan DMA, dicari orde m yang
menghasilkan MSE data latih terkecil, dengan mencoba beberapa kandidat m
(3 sampai 10).
m_candidates <- 3:10
mse_m <- sapply(m_candidates, function(m) {
sma_m <- SMA(train.ts, n = m)
err <- train.ts - c(NA, sma_m[-length(sma_m)])
mean(err[!is.na(err)]^2)
})
hasil_m <- data.frame(m = m_candidates, MSE_latih = mse_m)
hasil_m
## m MSE_latih
## 1 3 287.0107
## 2 4 310.1260
## 3 5 333.5234
## 4 6 342.3422
## 5 7 333.2544
## 6 8 314.4147
## 7 9 295.0424
## 8 10 275.2525
m_terbaik <- hasil_m$m[which.min(hasil_m$MSE_latih)]
m_terbaik
## [1] 10
Orde m dengan MSE data latih terkecil akan digunakan
pada pemulusan SMA dan DMA berikut ini (m = 10).
data.sma <- SMA(train.ts, n = m_terbaik)
data.ramal.sma <- c(NA, data.sma)
# forecast n_test periode ke depan (SMA -> nilai ramalan konstan)
ramalan_sma_test <- rep(data.ramal.sma[length(data.ramal.sma)], n_test)
data.gab.sma <- cbind(
aktual = c(train.ts, test.ts),
pemulusan = c(data.sma, rep(NA, n_test)),
ramalan = c(data.ramal.sma, ramalan_sma_test[-1])
)
tail(data.gab.sma, 10)
## aktual pemulusan ramalan
## [147,] 52.4000 NA 70.03167
## [148,] 68.1667 NA 70.03167
## [149,] 66.9458 NA 70.03167
## [150,] 64.2833 NA 70.03167
## [151,] 69.7833 NA 70.03167
## [152,] 87.8958 NA 70.03167
## [153,] 89.0917 NA 70.03167
## [154,] 86.1125 NA 70.03167
## [155,] 40.7583 NA 70.03167
## [156,] 73.1750 NA 70.03167
error_train.sma <- train.ts - data.ramal.sma[1:length(train.ts)]
idx <- (m_terbaik + 1):length(train.ts)
SSE_train.sma <- sum(error_train.sma[idx]^2)
MSE_train.sma <- mean(error_train.sma[idx]^2)
RMSE_train.sma <- sqrt(MSE_train.sma)
MAPE_train.sma <- mean(abs(error_train.sma[idx] / train.ts[idx]) * 100)
akurasi_train.sma <- data.frame(SSE = SSE_train.sma, MSE = MSE_train.sma,
RMSE = RMSE_train.sma, MAPE = MAPE_train.sma)
akurasi_train.sma
## SSE MSE RMSE MAPE
## 1 31378.79 275.2525 16.59074 20.18208
error_test.sma <- test.ts - ramalan_sma_test
SSE_test.sma <- sum(error_test.sma^2)
MSE_test.sma <- mean(error_test.sma^2)
RMSE_test.sma <- sqrt(MSE_test.sma)
MAPE_test.sma <- mean(abs(error_test.sma / test.ts) * 100)
akurasi_test.sma <- data.frame(SSE = SSE_test.sma, MSE = MSE_test.sma,
RMSE = RMSE_test.sma, MAPE = MAPE_test.sma)
akurasi_test.sma
## SSE MSE RMSE MAPE
## 1 5374.037 167.9387 12.95911 16.19147
DMA dilakukan dengan merata-ratakan hasil SMA sekali lagi (moving average dari moving average), untuk mengoreksi bias jika terdapat tren.
Sp <- SMA(train.ts, n = m_terbaik) # M't
Spp <- SMA(Sp, n = m_terbaik) # M''t
at <- 2 * Sp - Spp
bt <- (2 / (m_terbaik - 1)) * (Sp - Spp)
# forecast 1 periode ke depan pada data latih
ramalan_dma_train <- c(NA, at[-length(at)] + bt[-length(bt)])
data.gab.dma <- cbind(aktual = train.ts, Sp = Sp, Spp = Spp,
at = at, bt = bt, ramalan = ramalan_dma_train)
tail(data.gab.dma, 10)
## Time Series:
## Start = 115
## End = 124
## Frequency = 1
## aktual Sp Spp at bt ramalan
## 115 60.9333 65.46374 70.26145 60.66603 -1.06615733 62.36571
## 116 64.1542 65.98333 69.69178 62.27488 -0.82410044 59.59987
## 117 76.4042 67.34625 69.15103 65.54147 -0.40106289 61.45078
## 118 66.8083 69.07708 68.92583 69.22833 0.03361222 65.14040
## 119 71.3792 70.49042 68.73466 72.24618 0.39016889 69.26195
## 120 70.0375 70.38792 68.57225 72.20359 0.40348333 72.63635
## 121 64.3875 69.15792 68.35300 69.96284 0.17887178 72.60708
## 122 75.0167 68.60667 68.20954 69.00380 0.08825111 70.14171
## 123 85.8000 69.86417 68.33967 71.38867 0.33877867 69.09205
## 124 65.3958 70.03167 68.64092 71.42242 0.30905622 71.72745
error_train.dma <- train.ts - ramalan_dma_train
idx2 <- which(!is.na(error_train.dma))
SSE_train.dma <- sum(error_train.dma[idx2]^2)
MSE_train.dma <- mean(error_train.dma[idx2]^2)
RMSE_train.dma <- sqrt(MSE_train.dma)
MAPE_train.dma <- mean(abs(error_train.dma[idx2] / train.ts[idx2]) * 100)
akurasi_train.dma <- data.frame(SSE = SSE_train.dma, MSE = MSE_train.dma,
RMSE = RMSE_train.dma, MAPE = MAPE_train.dma)
akurasi_train.dma
## SSE MSE RMSE MAPE
## 1 35215.39 335.3847 18.31351 22.01043
Ramalan untuk h periode ke depan pada DMA menggunakan persamaan \(F_{n+m} = a_n + b_n \cdot m\), dengan \(a_n, b_n\) diambil dari nilai terakhir pada data latih.
at_n <- at[length(at)]
bt_n <- bt[length(bt)]
m_ahead <- 1:n_test
ramalan_dma_test <- at_n + bt_n * m_ahead
error_test.dma <- test.ts - ramalan_dma_test
SSE_test.dma <- sum(error_test.dma^2)
MSE_test.dma <- mean(error_test.dma^2)
RMSE_test.dma <- sqrt(MSE_test.dma)
MAPE_test.dma <- mean(abs(error_test.dma / test.ts) * 100)
akurasi_test.dma <- data.frame(SSE = SSE_test.dma, MSE = MSE_test.dma,
RMSE = RMSE_test.dma, MAPE = MAPE_test.dma)
akurasi_test.dma
## SSE MSE RMSE MAPE
## 1 7503.033 234.4698 15.31241 20.88182
SES cocok digunakan untuk data dengan pola stasioner/konstan. Nilai
parameter alpha dioptimalkan dengan alpha = NULL sehingga R
mencari alpha yang meminimumkan SSE data latih.
ses.opt <- ses(train.ts, h = n_test, alpha = NULL)
ses.opt$model
## Simple exponential smoothing
##
## Call:
## ses(y = train.ts, h = n_test, alpha = NULL)
##
## Smoothing parameters:
## alpha = 0.9999
##
## Initial states:
## l = 78.912
##
## sigma: 14.7142
##
## AIC AICc BIC
## 1268.525 1268.725 1276.986
autoplot(ses.opt) +
autolayer(fitted(ses.opt), series = "Fitted") +
ylab("PM2.5 (ug/m3)") + xlab("Periode") +
ggtitle("SES - Data Latih dan Hasil Peramalan")
Perhitungan akurasi data latih dilakukan langsung dari nilai residual
(ses.opt$residuals = data aktual - nilai fitted) agar tidak
bergantung pada nama field internal objek ets yang dapat
berbeda antar versi package forecast.
resid_train.ses <- ses.opt$residuals
SSE_train.ses <- sum(resid_train.ses^2)
MSE_train.ses <- mean(resid_train.ses^2)
RMSE_train.ses <- sqrt(MSE_train.ses)
MAPE_train.ses <- mean(abs(resid_train.ses / train.ts) * 100)
akurasi_train.ses <- data.frame(SSE = SSE_train.ses, MSE = MSE_train.ses,
RMSE = RMSE_train.ses, MAPE = MAPE_train.ses)
akurasi_train.ses
## SSE MSE RMSE MAPE
## 1 26414.07 213.0167 14.59509 17.03032
ramalan_ses_test <- as.numeric(ses.opt$mean)
error_test.ses <- test.ts - ramalan_ses_test
SSE_test.ses <- sum(error_test.ses^2)
MSE_test.ses <- mean(error_test.ses^2)
RMSE_test.ses <- sqrt(MSE_test.ses)
MAPE_test.ses <- mean(abs(error_test.ses / test.ts) * 100)
akurasi_test.ses <- data.frame(SSE = SSE_test.ses, MSE = MSE_test.ses,
RMSE = RMSE_test.ses, MAPE = MAPE_test.ses)
akurasi_test.ses
## SSE MSE RMSE MAPE
## 1 5714.91 178.591 13.36379 14.99509
DES (metode Holt, dua parameter) digunakan untuk memeriksa apakah memasukkan komponen tren dapat memperbaiki hasil peramalan, meskipun dari eksplorasi awal data cenderung tidak menunjukkan tren yang kuat.
des.opt <- HoltWinters(train.ts, gamma = FALSE)
des.opt
## Holt-Winters exponential smoothing with trend and without seasonal component.
##
## Call:
## HoltWinters(x = train.ts, gamma = FALSE)
##
## Smoothing parameters:
## alpha: 1
## beta : 0.07913238
## gamma: FALSE
##
## Coefficients:
## [,1]
## a 65.3958000
## b -0.3866241
ramalan.des <- forecast(des.opt, h = n_test)
autoplot(ramalan.des) + ylab("PM2.5 (ug/m3)") + xlab("Periode") +
ggtitle("DES (Holt) - Hasil Peramalan Data Uji")
Objek HoltWinters menyimpan langsung nilai
SSE (sum of squared errors) pada elemen $SSE.
Nilai MSE dihitung dengan membagi SSE dengan banyaknya titik fitted
(karena beberapa observasi awal hilang akibat inisialisasi level &
tren).
SSE_train.des <- des.opt$SSE
fitted.des <- fitted(des.opt)[, 1]
actual.des <- train.ts[(length(train.ts) - length(fitted.des) + 1):length(train.ts)]
MSE_train.des <- SSE_train.des / length(fitted.des)
RMSE_train.des <- sqrt(MSE_train.des)
MAPE_train.des <- mean(abs((actual.des - fitted.des) / actual.des) * 100)
akurasi_train.des <- data.frame(SSE = SSE_train.des, MSE = MSE_train.des,
RMSE = RMSE_train.des, MAPE = MAPE_train.des)
akurasi_train.des
## SSE MSE RMSE MAPE
## 1 29098.58 238.5129 15.44386 18.12265
ramalan_des_test <- as.numeric(ramalan.des$mean)
error_test.des <- test.ts - ramalan_des_test
SSE_test.des <- sum(error_test.des^2)
MSE_test.des <- mean(error_test.des^2)
RMSE_test.des <- sqrt(MSE_test.des)
MAPE_test.des <- mean(abs(error_test.des / test.ts) * 100)
akurasi_test.des <- data.frame(SSE = SSE_test.des, MSE = MSE_test.des,
RMSE = RMSE_test.des, MAPE = MAPE_test.des)
akurasi_test.des
## SSE MSE RMSE MAPE
## 1 8796.957 274.9049 16.58026 17.86193
Perbandingan dilakukan berdasarkan metrik akurasi pada data uji, karena performa pada data uji mencerminkan kemampuan generalisasi model terhadap data baru yang belum “dilihat” saat pemulusan.
perbandingan <- data.frame(
Metode = c("SMA", "DMA", "SES", "DES"),
SSE = c(SSE_test.sma, SSE_test.dma, SSE_test.ses, SSE_test.des),
MSE = c(MSE_test.sma, MSE_test.dma, MSE_test.ses, MSE_test.des),
RMSE = c(RMSE_test.sma, RMSE_test.dma, RMSE_test.ses, RMSE_test.des),
MAPE = c(MAPE_test.sma, MAPE_test.dma, MAPE_test.ses, MAPE_test.des)
)
perbandingan[order(perbandingan$MAPE), ]
## Metode SSE MSE RMSE MAPE
## 3 SES 5714.910 178.5910 13.36379 14.99509
## 1 SMA 5374.037 167.9387 12.95911 16.19147
## 4 DES 8796.957 274.9049 16.58026 17.86193
## 2 DMA 7503.033 234.4698 15.31241 20.88182
metode_terbaik <- perbandingan$Metode[which.min(perbandingan$MAPE)]
metode_terbaik
## [1] "SES"
ggplot(perbandingan, aes(x = reorder(Metode, MAPE), y = MAPE, fill = Metode)) +
geom_col() +
geom_text(aes(label = round(MAPE, 2)), vjust = -0.3) +
labs(x = "Metode", y = "MAPE Data Uji (%)",
title = "Perbandingan MAPE Data Uji Antar Metode Pemulusan") +
theme_bw() + theme(legend.position = "none")
Berdasarkan hasil perbandingan metrik akurasi pada data uji, metode dengan nilai MAPE terkecil adalah SES, sehingga metode ini dipilih sebagai metode pemulusan terbaik untuk data PM2.5 pada bagian Anggota 2.
Beberapa hal yang mendasari hasil ini: