Learn by Building 8: Time Series & Forecasting
Introduction
Time series adalah suatu object dalam statistik dimana object tersebut berhubungan dengan suatu deret waktu tertentu. Objek ini banyak ditemui dalam kehidupan sehari-hari, contoh: harga daging sapi harian, curah hujan bulanan, kuantitas penumpang bulanan, pendapatan tahunan, dll.
Analisis time series berhubungan dengan suatu data yang memiliki nilai numerik pada interval waktu tertentu. Proses untuk memprediksi nilai pada anilisis time series disebut sebagai peramalan atau forecasting. Ide utama dalam melakukan forecasting itu adalah korelasi dari data numerik.
Karakteristik
Time series data: data yang berhubungan dengan waktu dan memiliki interval waktu yang tetap/sama.
💡 Syarat utama data time series:
- Data harus urut sesuai periode waktu dari data terlama sampai ke data terbaru
- Interval waktunya harus tetap/sama
- Tidak boleh ada data yang terlewat untuk setiap interval
- Tidak boleh ada yang missing
Konten
Tabel di bawah menunjukkan data pemindaian ritel mingguan 2018 untuk volume (unit) dan harga ritel Nasional. Data pemindaian ritel datang langsung dari kasir pengecer berdasarkan penjualan eceran aktual alpukat Hass. Mulai tahun 2013, tabel di bawah mencerminkan kumpulan data ritel multi-outlet yang diperluas. Pelaporan multi-outlet mencakup agregasi saluran berikut: grosir, massa, klub, obat-obatan, dolar dan militer.
Harga Rata-Rata (alpukat) di tabel mencerminkan biaya per unit (per alpukat), bahkan ketika beberapa unit (alpukat) dijual dalam kantong. Kode Pencarian Produk / Product Lookup codes (PLU) di tabel hanya untuk alpukat Hass. Varietas alpukat lainnya (misalnya kulit hijau) tidak termasuk dalam tabel ini.
Langkah-langkah
- Tentukan apa masalahnya: Dalam hal ini kita ingin memiliki perkiraan harga Alpukat yang akurat.
- Mengumpulkan Informasi: Memahami proses apa yang digunakan untuk mengumpulkan informasi dan apakah informasi tersebut cukup untuk memiliki model prediksi yang efektif.
- Menerapkan Analisis Eksplorasi: Tentukan apakah ada pola apa pun dalam data kita sebelum membangun model.
- Memilih model prediktif: Ini adalah fase di mana kita memutuskan model mana yang paling tepat untuk membuat peramalan kita paling efektif.
- Menguji model kita: Analisis apakah model kita cukup efektif untuk membuat prediksi yang efektif.
Referensi
Forecasting: Principles and Practice by Rob J Hyndman and George Athanasopoulos
Import Library
suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(skimr))
suppressPackageStartupMessages(library(GGally))
suppressPackageStartupMessages(library(viridis))
suppressPackageStartupMessages(library(caret))
suppressPackageStartupMessages(library(e1071))
suppressPackageStartupMessages(library(rpart))
suppressPackageStartupMessages(library(xgboost))
suppressPackageStartupMessages(library(corrplot))
suppressPackageStartupMessages(library(corrgram))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(ggthemes))
suppressPackageStartupMessages(library(psych))
suppressPackageStartupMessages(library(scales))
suppressPackageStartupMessages(library(treemap))
suppressPackageStartupMessages(library(repr))
suppressPackageStartupMessages(library(cowplot))
suppressPackageStartupMessages(library(magrittr))
suppressPackageStartupMessages(library(ggpubr))
suppressPackageStartupMessages(library(RColorBrewer))
suppressPackageStartupMessages(library(plotrix))
suppressPackageStartupMessages(library(ggrepel))
suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(gridExtra))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(tibbletime))
suppressPackageStartupMessages(library(reshape2))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggpubr))
suppressPackageStartupMessages(library(grid))
suppressPackageStartupMessages(library(smooth))
suppressPackageStartupMessages(library(forecast))
suppressPackageStartupMessages(library(fpp2))Read Data
avocado <- read.csv("avocado.csv")Data Wrangling
avocado <- avocado %>%
select(-X, -Total.Bags, -Small.Bags, -Large.Bags, -XLarge.Bags)
head(avocado)#> Date AveragePrice Total.Volume X4046 X4225 X4770 type
#> 1 2015-12-27 1.33 64236.62 1036.74 54454.85 48.16 conventional
#> 2 2015-12-20 1.35 54876.98 674.28 44638.81 58.33 conventional
#> 3 2015-12-13 0.93 118220.22 794.70 109149.67 130.50 conventional
#> 4 2015-12-06 1.08 78992.15 1132.00 71976.41 72.58 conventional
#> 5 2015-11-29 1.28 51039.60 941.48 43838.39 75.78 conventional
#> 6 2015-11-22 1.26 55979.78 1184.27 48067.99 43.61 conventional
#> year region
#> 1 2015 Albany
#> 2 2015 Albany
#> 3 2015 Albany
#> 4 2015 Albany
#> 5 2015 Albany
#> 6 2015 Albany
Keterangan:
Date: tanggal observasiAveragePrice: harga rata-rata satu buah alpukatTotal Volume: jumlah total alpukat yang terjualX4046: Jumlah total alpukat dengan PLU 4046 terjualX4225: Jumlah total alpukat dengan PLU 4225 terjualX4770: Jumlah total alpukat dengan PLU 4770 terjualtype: conventional atau organicyear: tahunRegion: kota atau wilayah observasi
Exploratory Data Analysis
Dalam fase proyek ini kita akan berkonsentrasi terutama pada dua aspek analisis peramalan deret waktu yang terdiri dari:
Pola Musiman: Pada bagian ini kita akan fokus pada pola konstan yang sering terjadi dari tahun ke tahun dan dari bulan ke bulan baik pada jenis alpukat konvensional maupun organik.
Pola Siklus: Bagian ini akan meminta kita untuk mencari lebih dalam faktor-faktor apa yang dapat mempengaruhi harga alpukat selama tahun-tahun tersebut (persediaan rendah, badai, dll.) dan akan menarik untuk melihat apakah salah satu faktor tersebut berkontribusi pada perubahan harga yang signifikan.
1. Distribusi Rata-rata Harga Alpukat berdasarkan Tipe
options(repr.plot.width=8, repr.plot.height=4)
ggplot(avocado,
aes(x=AveragePrice, fill=type)) +
geom_density() +
facet_wrap(~type) +
theme_minimal() +
theme(plot.title=element_text(hjust=0.5),
legend.position="bottom") +
labs(title="Avocado Price by Type") +
scale_fill_brewer(palette="Set2")2. Proporsi Jenis Alpukat berdasarkan Rata-rata Volume
vol_type <- avocado %>%
group_by(type) %>%
summarise(avg.vol=mean(Total.Volume)) %>%
mutate(pct=prop.table(avg.vol) * 100)
vol_type#> # A tibble: 2 × 3
#> type avg.vol pct
#> <chr> <dbl> <dbl>
#> 1 conventional 1653213. 97.2
#> 2 organic 47811. 2.81
Di bagian ini kita akan menganalisis berbagai jenis alpukat yang kita miliki dalam kumpulan data ini. Pada dasarnya, kita memiliki dua jenis alpukat yang terjual:
- Konvensional (sebanyak 97%)
- Organik (sebanyak 3%)
3. Melihat Trend Harga berdasarkan Waktu
# Mengubah tipe data pada kolom date dari factor menjadi date
avocado$Date <- as.Date(avocado$Date, "%Y-%m-%d")
class(avocado$Date)#> [1] "Date"
# Mengurutkan date
avocado <- avocado[order(as.Date(avocado$Date, format="%Y-%m-%d")),]price_trend <- avocado %>%
select(Date, AveragePrice, type) %>%
ggplot(aes(x=Date, y=AveragePrice)) +
geom_area(aes(color=type, fill=type), alpha = 0.3, position = position_dodge(0.8)) +
theme_minimal() +
scale_color_manual(values = c("#ED7921", "#62BE51")) +
scale_fill_manual(values = c("#FD833E", "#B8FC5F"))
price_trend
Insight:
- Alpukat organik: Berdasarkan perubahan harga sepanjang waktu kita dapat melihat bahwa mereka lebih mahal.
- Alpukat konvensional: Berdasarkan perubahan harga sepanjang waktu, kita dapat melihat bahwa harganya lebih murah.
# Membuat Facet Wrap untuk setiap jenis produk
ggplot(data = avocado,
aes(x = Date, y = AveragePrice, col=type)) +
geom_line() +
facet_wrap(~ type) +
theme_minimal() +
theme(legend.position="bottom")4. Hubungan antara Harga dan Volume Total
# Filter by type
organic <- avocado %>%
select(Date, AveragePrice, type, Total.Volume) %>%
filter(type == "organic")
conventional <- avocado %>%
select(Date, AveragePrice, type, Total.Volume) %>%
filter(type == "conventional")# Organic Avocadoes
organic <- as_tbl_time(organic, index=Date)
organic <- as_period(organic, '1 month')
# Conventional Avocadoes
conventional <- as_tbl_time(conventional, index=Date)
conventional <- as_period(conventional, '1 month')options(repr.plot.width=8, repr.plot.height=6)
# Harga bulanan avocadoes konvensional
conventional_monthly <- conventional %>%
ggplot(aes(x=Date, y=AveragePrice)) +
geom_line(color="#7FB3D5") +
theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#D5D8DC")) +
labs(title="Conventional Avocados") +
geom_hline(yintercept=max(conventional$AveragePrice), linetype="dashed", color = "red") +
geom_hline(yintercept=min(conventional$AveragePrice), linetype="dashed", color = "blue")
# Volume chart avocadoes konvensional
conventional_volume <- conventional %>%
ggplot(aes(x=Date, y=Total.Volume)) +
geom_bar(stat='identity', fill="#7FB3D5", color="black") +
theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#D5D8DC")) +
geom_smooth(method="loess", color="red")
# Harga bulanan avocadoes organic
organic_monthly <- organic %>%
ggplot(aes(x=Date, y=AveragePrice)) +
geom_line(color="#58D68D") +
theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#D5D8DC")) +
labs(title="Organic Avocados") +
geom_hline(yintercept=max(organic$AveragePrice), linetype="dashed", color = "red") +
geom_hline(yintercept=min(organic$AveragePrice), linetype="dashed", color = "blue")
# Volume chart avocadoes organic
organic_volume <- organic %>%
ggplot(aes(x=Date, y=Total.Volume)) +
geom_bar(stat='identity', fill="#58D68D",color="black") +
theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#D5D8DC")) +
geom_smooth(method="loess", color="red")
plot_grid(conventional_monthly, organic_monthly,conventional_volume, organic_volume, nrow=2, ncol=2)
Insight:
Pada fase ini kita akan menganalisis dampak penawaran terhadap harga alpukat secara keseluruhan. Biasanya, ada hubungan terbalik antara penawaran dan harga. Ketika terjadi kelebihan produksi alpukat maka akan berdampak negatif pada harga pasar alpukat.
Mari kita lihat apakah ini berlaku untuk alpukat konvensional dan organik:
- Konvensional: Pada akhir tahun 2017 kita dapat melihat penurunan harga yang besar, pada saat yang sama terjadi peningkatan volume alpukat di pasar.
- Organik: Sama halnya dengan alpukat organik, pada akhir tahun 2017 terjadi penurunan yang cukup besar dan kita dapat melihat peningkatan volume yang sangat besar.
- Puncak volume: Perhatikan bagaimana setiap puncak volume merupakan sinyal untuk penurunan harga alpukat yang akan datang.
Data Pre-Processing
Beberapa Jenis Pola yang akan kita lihat:
- Seasonal / Musiman: Fluktuasi memang terjadi pada frekuensi tetap.
- Cyclical / Siklus: Fluktuasi tidak terjadi pada frekuensi tetap.
- Trends / Tren: Ini terjadi ketika ada peningkatan atau penurunan yang konsisten dalam data kita.
Model Building
Menganalisis Pola Seaonal (Musiman)
Pada bagian ini kita akan mencoba mencari apakah ada pola musiman berulang yang signifikan. Maksud kita jika ada tren berulang di mana harga alpukat cenderung meningkat. Misalnya, pada bulan Mei setiap tahun kita melihat bahwa harga alpukat cenderung meningkat karena beberapa alasan tertentu.
seasonal_avocado <- avocado
seasonal_avocado$month_year <- format(as.Date(avocado$Date), "%Y-%m")
seasonal_avocado$month <- format(as.Date(avocado$Date), "%m")
seasonal_avocado$year <- format(as.Date(avocado$Date), "%Y")
seasonal_avocado$monthabb <- sapply(seasonal_avocado$month, function(x) month.abb[as.numeric(x)])
seasonal_avocado$monthabb = factor(seasonal_avocado$monthabb, levels = month.abb)
# Melihat apakah ada seasonal patterns
ggplot(seasonal_avocado, aes(x = AveragePrice, fill = as.factor(year))) +
geom_density(alpha = .5) +
theme_economist() +
facet_wrap(~ year) +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F9E79F")) +
guides(fill = FALSE) +
labs(title="Distribution of Prices by year", x = 'Average Price', y = 'Density') +
scale_fill_manual(values=c("#2E64FE", "#40FF00", "#FE642E", "#FE2E2E"))
Insights:
- Distribusi per tahun: Tampaknya sebagian besar harga di tahun 2015 berada di $1,00. Sedangkan untuk tahun 2016 dan 2017 kepadatan harga sedikit lebih tinggi.
- Penurunan harga yang besar pada akhir tahun: Cukup menarik kita melihat bahwa pada akhir tahun terjadi penurunan harga yang besar pada harga alpukat. kita bertanya-tanya mengapa itu bisa terjadi? Alasan apa yang mungkin ada untuk memiliki harga yang lebih rendah pada akhir setiap tahun.
- Standar deviasi sebagai ukuran volatilitas: Standar deviasi hanyalah akar kuadrat dari varians. Kita dapat melihat bahwa selama tahun 2017, pasar alpukat mengalami volatilitas tertinggi baik untuk alpukat konvensional maupun organik.
- 2017 tahun yang baik untuk alpukat: Berdasarkan tren ini, kita dapat melihat bahwa 2017 adalah tahun yang baik untuk alpukat.
# Detecting seasonality patterns (conventional)
conv_patterns <- seasonal_avocado %>%
select(monthabb, AveragePrice, type) %>%
filter(type == "conventional") %>%
group_by(monthabb) %>%
summarize(avg=mean(AveragePrice)) %>%
ggplot(aes(x=monthabb, y=avg)) +
geom_point(color="#F35D5D", aes(size=avg)) +
geom_line(group=1, color="#7FB3D5") +
theme_economist() +
theme(legend.position="none", plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F9E79F")) +
labs(title="Conventional Avocados", x="Month", y="Average Price")
# Detecting seasonality patterns (organic)
org_patterns <- seasonal_avocado %>%
select(monthabb, AveragePrice, type) %>%
filter(type == "organic") %>%
group_by(monthabb) %>%
summarize(avg=mean(AveragePrice)) %>%
ggplot(aes(x=monthabb, y=avg)) +
geom_point(color="#F35D5D", aes(size=avg)) +
geom_line(group=1, color="#58D68D") +
theme_economist() +
theme(legend.position="none", plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F9E79F")) +
labs(title="Organic Avocados", x="Month", y="Average Price")
plot_grid(conv_patterns, org_patterns, nrow=2)
Insights:
- Puncak harga per Bulan: Tampaknya sebagian besar puncak harga terjadi untuk alpukat konvensional dan organik antara bulan September dan Oktober.
- Penurunan harga yang besar pada akhir tahun: Cukup menarik kita melihat bahwa pada akhir tahun terjadi penurunan harga yang besar pada harga alpukat. kita bertanya-tanya mengapa itu bisa terjadi? Alasan apa yang mungkin ada untuk memiliki harga yang lebih rendah pada akhir setiap tahun.
Berdasarkan tren ini, kita dapat melihat bahwa 2017 adalah tahun yang baik untuk alpukat, apakah ramalan kita (nanti dalam proyek ini) memprediksi bahwa hal itu akan terjadi di tahun 2018?
# Hmm let's see if the Seasonality pattern is maintained each year.
options(repr.plot.width=8, repr.plot.height=6)
# conventional each year
conv_pat_yearly <- seasonal_avocado %>%
select(year, monthabb, AveragePrice, type) %>%
filter(type == "conventional", year == c("2015", "2016", "2017")) %>%
group_by(year, monthabb) %>%
summarize(avg=mean(AveragePrice)) %>%
ggplot(aes(x=monthabb, y=avg)) +
geom_point(color="#5D6D7E") +
geom_line(group=1, color="#F7DC6F") +
facet_wrap(~as.factor(year)) +
theme_minimal() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F4F6F7"), axis.text.x = element_text(angle = 90)) +
labs(title="Seasonal Fluctuations \n Conventional Avocados", x="Month", y="Average Price")
# organic each year
org_pat_yearly <- seasonal_avocado %>%
select(year, monthabb, AveragePrice, type) %>%
filter(type == "organic", year == c("2015", "2016", "2017")) %>%
group_by(year, monthabb) %>%
summarize(avg=mean(AveragePrice)) %>%
ggplot(aes(x=monthabb, y=avg)) +
geom_point(color="#5D6D7E") +
geom_line(group=1, color="#E74C3C") +
facet_wrap(~as.factor(year)) +
theme_minimal() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F4F6F7"), axis.text.x = element_text(angle = 90)) +
labs(title="Seasonal Fluctuations \n Organic Avocados", x="Month", y="Average Price")
plot_grid(conv_pat_yearly, org_pat_yearly, nrow=2)
Insights:
- Dapat kita lihat trend harga dari setiap tahunnya cenderung naik, dan di tahun 2017 setiap jenis avocado memiliki harga yang paling tinggi.
# Let's have a closer look how the price changes per month.
# filter by type and year
options(repr.plot.width=10, repr.plot.height=8)
se <- function(x) sqrt(var(x)/length(x))
# conventional each month
conv <- seasonal_avocado %>%
select(year, monthabb, AveragePrice, type) %>%
filter(type == "conventional", year == c("2015", "2016", "2017")) %>%
group_by(year, monthabb) %>%
ggplot(aes(x=monthabb, y=AveragePrice, fill=monthabb), color="white") +
geom_bar(width=1, stat='identity') +
geom_errorbar(aes(ymin = AveragePrice - se(AveragePrice),
ymax = AveragePrice + se(AveragePrice),
color = monthabb),
width = .2) +
scale_y_continuous(breaks = 0:nlevels(seasonal_avocado$monthabb)) +
facet_wrap(~year) +
theme_minimal() +
theme(axis.ticks = element_blank(),
axis.text.y=element_blank(),
axis.title = element_blank(),
axis.line = element_blank(),
plot.background=element_rect(fill="#FFF1E0"),
legend.position="none", plot.title=element_text(hjust=0.5)) +
coord_polar() +
labs(title="Seasonal cycle \n Conventional Avocados") +
scale_fill_manual(values=c('#57FCE0', '#57A6FC', '#3C546E', '#4AFA76', '#95CFA4', '#C0E436', '#F2A42D', '#F25F2D', '#F2442D',
'#AB4949', '#4950AB', '#4974AB'))
# organic each month
org <- seasonal_avocado %>%
select(year, monthabb, AveragePrice, type) %>%
filter(type == "organic", year == c("2015", "2016", "2017")) %>%
group_by(year, monthabb) %>%
ggplot(aes(x=monthabb, y=AveragePrice, fill=monthabb), color="white") +
geom_bar(width=1, stat='identity') +
geom_errorbar(aes(ymin = AveragePrice - se(AveragePrice),
ymax = AveragePrice + se(AveragePrice),
color = monthabb),
width = .2) + scale_y_continuous(breaks = 0:nlevels(seasonal_avocado$monthabb)) +
facet_wrap(~year) +
theme_minimal() +
theme(axis.ticks = element_blank(),
axis.text.y=element_blank(),
axis.title = element_blank(),
axis.line = element_blank(),
plot.background=element_rect(fill="#FFF1E0"),
legend.position="none", plot.title=element_text(hjust=0.5)) +
coord_polar() +
labs(title="Seasonal cycle \n Conventional Avocados") +
scale_fill_manual(values=c('#57FCE0', '#57A6FC', '#3C546E', '#4AFA76', '#95CFA4', '#C0E436', '#F2A42D', '#F25F2D', '#F2442D',
'#AB4949', '#4950AB', '#4974AB'))
grid.arrange(conv, org, nrow = 2)Forecast & Evaluation
Di bagian ini kita akan menggunakan berbagai model untuk menghasilkan prediksi yang akurat untuk melihat seperti apa pola yang akan datang untuk harga alpukat.
Daftar model yang akan kita gunakan meliputi:
- Smoothing Moving Average
- Seasonal Naive Method
- Drift Method
- ARIMA
Smoothing Moving Average
# Using Smoothing average
sma_conv <- sma(conventional$AveragePrice, h=10, silent=FALSE) + theme_economist()Seasonal Naive Method
Sebelum menjelaskan bagaimana jenis model peramalan ini bekerja, ada beberapa hal yang harus kita pahami:
- Average Method: Ini hanyalah garis rata-rata dari tiga tahun terakhir yang kita analisis. Tahun-tahun ini adalah 2015, 2016 dan 2017. Ini akan memberi kita nilai tetap, tetapi nilai ini penting. Ketika kita berbicara tentang analisis teknis, setiap kali harga turun di bawah garis rata-rata, kita “diharapkan” melihat tren turun.
- Naive Method: Hanya mengatur semua nilai perkiraan ke nilai terakhir yang kita lihat. Anda dapat melihat bahwa garis biru berada pada level yang sama dengan harga terakhir di bulan Desember. Itu menjelaskan mengapa sangat rendah! Karena harga alpukat sangat rendah untuk alpukat konvensional dan organik, ini bisa memiliki fungsi yang mirip dengan garis dukungan. Jika kita melihat harga bergerak di bawah garis ini, itu bisa menunjukkan tren penurunan baru.
- Seasonal Naive Method: Ini akan sama dengan nilai terakhir tahun lalu. Jadi misalnya jika kita menggunakan data bulanan, perkiraan kita di bulan Januari akan sama dengan nilai Januari terakhir tahun 2018.
- Drift Method: Ini akan mendeteksi pola harga dengan lebih baik karena akan menggunakan rata-rata perubahan data historis sebagai titik acuan dalam hal peramalan. Kemungkinan besar, ramalan ini akan memahami tren, namun akan mengabaikan pola musiman apa pun.
library(fpp2)
# Let's declare our data as time series
conv <- avocado %>%
select(Date, AveragePrice, type) %>%
filter(type == "conventional")
org <- avocado %>%
select(Date, AveragePrice, type) %>%
filter(type == "organic")
# Organic Avocados
conventional <- as_tbl_time(conv, index=Date)
conventional <- as_period(conventional, '1 month')
conventional$type <- NULL
# Organic Avocados
organic <- as_tbl_time(org, index=Date)
organic <- as_period(organic, '1 month')
organic$type <- NULL
conv_ts <- ts(conventional[,2], start=c(2015, 1), frequency=12)
org_ts <- ts(organic[,2], start=c(2015, 1), frequency=12)
# The difference from month to month
# To remove the trend we take the first difference
differences_conv <- diff(conv_ts)
main_diff <- autoplot(differences_conv) + theme_minimal()
seasonality_diff <- ggseasonplot(differences_conv) + theme_minimal()
plot_grid(main_diff, seasonality_diff, nrow=2)ARIMA
# Y has trend unlike difference, it will take the difference behind the scenes d=1
# Stepwise will only use some models instead of all possible combinations
# approximation uses the model that approximates the best result to save time
arima_model_cv <- auto.arima(conv_ts, d=1, D=1, stepwise=FALSE, approximation=FALSE, trace=TRUE)#>
#> ARIMA(0,1,0)(0,1,0)[12] : 17.51894
#> ARIMA(0,1,0)(0,1,1)[12] : Inf
#> ARIMA(0,1,0)(1,1,0)[12] : 13.65754
#> ARIMA(0,1,0)(1,1,1)[12] : Inf
#> ARIMA(0,1,1)(0,1,0)[12] : 11.44504
#> ARIMA(0,1,1)(0,1,1)[12] : Inf
#> ARIMA(0,1,1)(1,1,0)[12] : 7.926653
#> ARIMA(0,1,1)(1,1,1)[12] : Inf
#> ARIMA(0,1,2)(0,1,0)[12] : 13.85906
#> ARIMA(0,1,2)(0,1,1)[12] : Inf
#> ARIMA(0,1,2)(1,1,0)[12] : 10.60394
#> ARIMA(0,1,2)(1,1,1)[12] : Inf
#> ARIMA(0,1,3)(0,1,0)[12] : Inf
#> ARIMA(0,1,3)(0,1,1)[12] : Inf
#> ARIMA(0,1,3)(1,1,0)[12] : Inf
#> ARIMA(0,1,3)(1,1,1)[12] : Inf
#> ARIMA(0,1,4)(0,1,0)[12] : Inf
#> ARIMA(0,1,4)(0,1,1)[12] : Inf
#> ARIMA(0,1,4)(1,1,0)[12] : Inf
#> ARIMA(0,1,5)(0,1,0)[12] : Inf
#> ARIMA(1,1,0)(0,1,0)[12] : 15.40448
#> ARIMA(1,1,0)(0,1,1)[12] : Inf
#> ARIMA(1,1,0)(1,1,0)[12] : 10.08229
#> ARIMA(1,1,0)(1,1,1)[12] : Inf
#> ARIMA(1,1,1)(0,1,0)[12] : 13.96476
#> ARIMA(1,1,1)(0,1,1)[12] : Inf
#> ARIMA(1,1,1)(1,1,0)[12] : 10.64378
#> ARIMA(1,1,1)(1,1,1)[12] : Inf
#> ARIMA(1,1,2)(0,1,0)[12] : Inf
#> ARIMA(1,1,2)(0,1,1)[12] : Inf
#> ARIMA(1,1,2)(1,1,0)[12] : Inf
#> ARIMA(1,1,2)(1,1,1)[12] : Inf
#> ARIMA(1,1,3)(0,1,0)[12] : Inf
#> ARIMA(1,1,3)(0,1,1)[12] : Inf
#> ARIMA(1,1,3)(1,1,0)[12] : Inf
#> ARIMA(1,1,4)(0,1,0)[12] : Inf
#> ARIMA(2,1,0)(0,1,0)[12] : 12.20614
#> ARIMA(2,1,0)(0,1,1)[12] : Inf
#> ARIMA(2,1,0)(1,1,0)[12] : 10.50862
#> ARIMA(2,1,0)(1,1,1)[12] : Inf
#> ARIMA(2,1,1)(0,1,0)[12] : 14.37757
#> ARIMA(2,1,1)(0,1,1)[12] : Inf
#> ARIMA(2,1,1)(1,1,0)[12] : 13.47267
#> ARIMA(2,1,1)(1,1,1)[12] : Inf
#> ARIMA(2,1,2)(0,1,0)[12] : 15.41755
#> ARIMA(2,1,2)(0,1,1)[12] : Inf
#> ARIMA(2,1,2)(1,1,0)[12] : Inf
#> ARIMA(2,1,3)(0,1,0)[12] : Inf
#> ARIMA(3,1,0)(0,1,0)[12] : 15.01092
#> ARIMA(3,1,0)(0,1,1)[12] : Inf
#> ARIMA(3,1,0)(1,1,0)[12] : 13.58839
#> ARIMA(3,1,0)(1,1,1)[12] : Inf
#> ARIMA(3,1,1)(0,1,0)[12] : 18.00893
#> ARIMA(3,1,1)(0,1,1)[12] : Inf
#> ARIMA(3,1,1)(1,1,0)[12] : 16.8641
#> ARIMA(3,1,2)(0,1,0)[12] : Inf
#> ARIMA(4,1,0)(0,1,0)[12] : 16.62101
#> ARIMA(4,1,0)(0,1,1)[12] : Inf
#> ARIMA(4,1,0)(1,1,0)[12] : 15.52644
#> ARIMA(4,1,1)(0,1,0)[12] : 18.49889
#> ARIMA(5,1,0)(0,1,0)[12] : 18.45644
#>
#>
#>
#> Best model: ARIMA(0,1,1)(1,1,0)[12]
arima_model_or <- auto.arima(org_ts, d=1, D=1, stepwise=FALSE, approximation=FALSE, trace=TRUE)#>
#> ARIMA(0,1,0)(0,1,0)[12] : 2.521273
#> ARIMA(0,1,0)(0,1,1)[12] : Inf
#> ARIMA(0,1,0)(1,1,0)[12] : -2.28856
#> ARIMA(0,1,0)(1,1,1)[12] : Inf
#> ARIMA(0,1,1)(0,1,0)[12] : 4.237894
#> ARIMA(0,1,1)(0,1,1)[12] : Inf
#> ARIMA(0,1,1)(1,1,0)[12] : -0.9972336
#> ARIMA(0,1,1)(1,1,1)[12] : Inf
#> ARIMA(0,1,2)(0,1,0)[12] : 6.536184
#> ARIMA(0,1,2)(0,1,1)[12] : Inf
#> ARIMA(0,1,2)(1,1,0)[12] : 1.305819
#> ARIMA(0,1,2)(1,1,1)[12] : Inf
#> ARIMA(0,1,3)(0,1,0)[12] : 9.325564
#> ARIMA(0,1,3)(0,1,1)[12] : Inf
#> ARIMA(0,1,3)(1,1,0)[12] : 3.595351
#> ARIMA(0,1,3)(1,1,1)[12] : Inf
#> ARIMA(0,1,4)(0,1,0)[12] : Inf
#> ARIMA(0,1,4)(0,1,1)[12] : Inf
#> ARIMA(0,1,4)(1,1,0)[12] : 7.006226
#> ARIMA(0,1,5)(0,1,0)[12] : Inf
#> ARIMA(1,1,0)(0,1,0)[12] : 4.34258
#> ARIMA(1,1,0)(0,1,1)[12] : Inf
#> ARIMA(1,1,0)(1,1,0)[12] : -0.7000006
#> ARIMA(1,1,0)(1,1,1)[12] : Inf
#> ARIMA(1,1,1)(0,1,0)[12] : Inf
#> ARIMA(1,1,1)(0,1,1)[12] : Inf
#> ARIMA(1,1,1)(1,1,0)[12] : Inf
#> ARIMA(1,1,1)(1,1,1)[12] : Inf
#> ARIMA(1,1,2)(0,1,0)[12] : 8.908865
#> ARIMA(1,1,2)(0,1,1)[12] : Inf
#> ARIMA(1,1,2)(1,1,0)[12] : Inf
#> ARIMA(1,1,2)(1,1,1)[12] : Inf
#> ARIMA(1,1,3)(0,1,0)[12] : 11.89595
#> ARIMA(1,1,3)(0,1,1)[12] : Inf
#> ARIMA(1,1,3)(1,1,0)[12] : 7.010278
#> ARIMA(1,1,4)(0,1,0)[12] : Inf
#> ARIMA(2,1,0)(0,1,0)[12] : 6.613931
#> ARIMA(2,1,0)(0,1,1)[12] : Inf
#> ARIMA(2,1,0)(1,1,0)[12] : 1.897622
#> ARIMA(2,1,0)(1,1,1)[12] : Inf
#> ARIMA(2,1,1)(0,1,0)[12] : 8.612471
#> ARIMA(2,1,1)(0,1,1)[12] : Inf
#> ARIMA(2,1,1)(1,1,0)[12] : Inf
#> ARIMA(2,1,1)(1,1,1)[12] : Inf
#> ARIMA(2,1,2)(0,1,0)[12] : Inf
#> ARIMA(2,1,2)(0,1,1)[12] : Inf
#> ARIMA(2,1,2)(1,1,0)[12] : Inf
#> ARIMA(2,1,3)(0,1,0)[12] : 14.56648
#> ARIMA(3,1,0)(0,1,0)[12] : 9.038767
#> ARIMA(3,1,0)(0,1,1)[12] : Inf
#> ARIMA(3,1,0)(1,1,0)[12] : Inf
#> ARIMA(3,1,0)(1,1,1)[12] : Inf
#> ARIMA(3,1,1)(0,1,0)[12] : 11.40005
#> ARIMA(3,1,1)(0,1,1)[12] : Inf
#> ARIMA(3,1,1)(1,1,0)[12] : Inf
#> ARIMA(3,1,2)(0,1,0)[12] : Inf
#> ARIMA(4,1,0)(0,1,0)[12] : 10.26243
#> ARIMA(4,1,0)(0,1,1)[12] : Inf
#> ARIMA(4,1,0)(1,1,0)[12] : Inf
#> ARIMA(4,1,1)(0,1,0)[12] : Inf
#> ARIMA(5,1,0)(0,1,0)[12] : 12.89994
#>
#>
#>
#> Best model: ARIMA(0,1,0)(1,1,0)[12]
print(summary(arima_model_cv))#> Series: conv_ts
#> ARIMA(0,1,1)(1,1,0)[12]
#>
#> Coefficients:
#> ma1 sar1
#> -0.5852 -0.5753
#> s.e. 0.1547 0.1753
#>
#> sigma^2 = 0.05354: log likelihood = -0.42
#> AIC=6.84 AICc=7.93 BIC=10.61
#>
#> Training set error measures:
#> ME RMSE MAE MPE MAPE MASE
#> Training set -0.001571837 0.181512 0.1213104 -1.236511 9.331712 0.4580951
#> ACF1
#> Training set -0.04030292
checkresiduals(arima_model_cv) + theme_minimal()#>
#> Ljung-Box test
#>
#> data: Residuals from ARIMA(0,1,1)(1,1,0)[12]
#> Q* = 5.909, df = 6, p-value = 0.4335
#>
#> Model df: 2. Total lags used: 8
#> NULL
options(repr.plot.width=10, repr.plot.height=7)
conv_forecast_sn <- autoplot(conv_ts) +
autolayer(meanf(conv_ts, h=24),
series="Mean", PI=FALSE) +
autolayer(naive(conv_ts, h=24),
series="Naïve", PI=FALSE) +
autolayer(snaive(conv_ts, h=24),
series="Seasonal naïve", PI=FALSE) +
ggtitle("Conventional Avocado \n Seasonal Naive Method") +
xlab("Date") + ylab("Price") + scale_color_manual(values=c("#FA5858", "#00BFFF", "#FF8000")) +
guides(colour=guide_legend(title="Forecast:")) + theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F4F6F7"))
org_forecast_sn <- autoplot(org_ts) +
autolayer(meanf(org_ts, h=24),
series="Mean", PI=FALSE) +
autolayer(naive(org_ts, h=24),
series="Naïve", PI=FALSE) +
autolayer(snaive(org_ts, h=24),
series="Seasonal naïve", PI=FALSE) +
ggtitle("Organic Avocado \n Seasonal Naive Method") +
xlab("Date") + ylab("Price") + scale_color_manual(values=c("#FA5858", "#00BFFF", "#FF8000")) +
guides(colour=guide_legend(title="Forecast:")) + theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#DCFCE6"), legend.position="none")
plot_grid(conv_forecast_sn, org_forecast_sn, nrow=2)conv_forecast_dr <- autoplot(conv_ts) +
autolayer(meanf(conv_ts, h=24),
series="Mean", PI=FALSE) +
autolayer(naive(conv_ts, h=24),
series="Naïve", PI=FALSE) +
autolayer(rwf(conv_ts, drift=TRUE, h=24),
series="Drift", PI=FALSE) +
ggtitle("Conventional Avocado \n Drift Method") +
xlab("Date") + ylab("Price") + scale_color_manual(values=c("#ffff24", "#98fb98", "#ff6347")) +
guides(colour=guide_legend(title="Forecast:")) + theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F4F6F7"))
org_forecast_dr <- autoplot(org_ts) +
autolayer(meanf(org_ts, h=24),
series="Mean", PI=FALSE) +
autolayer(rwf(org_ts, h=24),
series="Naïve", PI=FALSE) +
autolayer(rwf(org_ts, drift=TRUE, h=24),
series="Drift", PI=FALSE) +
ggtitle("Organic Avocado \n Drift Method") +
xlab("Date") + ylab("Price") + scale_color_manual(values=c("#ffff24", "#98fb98", "#ff6347")) +
guides(colour=guide_legend(title="Forecast:")) + theme_economist() +
theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#DCFCE6"), legend.position="none")
plot_grid(conv_forecast_dr, org_forecast_dr, nrow=2)Memahami Residual
Residual tidak hanya digunakan untuk memahami perbedaan antara model perkiraan dan nilai aktual, tetapi juga memberi tahu kita beberapa potensi pergerakan “abnormal” selama periode tertentu. Mari jelajahi grafik di bawah ini untuk alpukat konvensional dan organik. Berikut adalah temuan saya:
- Conventional Avocados: Ada dua puncak abnormal pada grafik alpukat konvensional. Ini mungkin menunjukkan pergerakan harga yang tidak normal yang dapat mengindikasikan bahwa ini disebabkan oleh beberapa peristiwa luar biasa.
- Organic Avocados: kita melihat beberapa puncak dalam alpukat organik tetapi tidak sejauh alpukat konvensional. Namun demikian, ada beberapa puncak abnormal yang harus dievaluasi lebih lanjut.
rescv_nv <- residuals(naive(conv_ts))
p1 <- autoplot(rescv_nv, color="#b6ff6c") + xlab("Day") + ylab("") +
ggtitle("Residuals from naïve method \n Conventional Avocados") + theme_economist() +
theme(plot.title=element_text(hjust=0.5, color="white"), plot.background=element_rect(fill="#0D7680"),
axis.text.x=element_text(colour="white"), axis.text.y=element_text(colour="white"),
axis.title=element_text(colour="white"))
resorg_nv <- residuals(naive(org_ts))
p2 <- autoplot(resorg_nv, color="#ffee6c") + xlab("Day") + ylab("") +
ggtitle("Residuals from naïve method \n Organic Avocados") + theme_economist() +
theme(plot.title=element_text(hjust=0.5, color="white"), plot.background=element_rect(fill="#0D7680"),
axis.text.x=element_text(colour="white"), axis.text.y=element_text(colour="white"),
axis.title=element_text(colour="white"))
plot_grid(p1, p2, nrow=2)Memahami ARIMA
- Auto Regressive (AR): Berarti titik waktu masa lalu dapat memiliki tingkat tertentu pengamatan waktu saat ini dan masa depan. Model ARIMA memperhitungkan pengamatan tertinggal untuk menghasilkan pengamatan perkiraan. Bobot ditambahkan ke pengamatan masa lalu, namun bobot dapat bervariasi pada seberapa baru pengamatan masa lalu. Semakin baru, semakin banyak bobot yang ditambahkan pada pengamatan terakhir yang terbaru.
- Integrated (I): Jika ada tren yang konsisten dalam pergerakan harga masa lalu, kemungkinan besar tidak stasioner artinya bahwa musiman tetap ada dalam pergerakan harga di masa lalu. Terintegrasi menghapus fase musiman dari kumpulan data kita jika ada pola yang konsisten yang menunjukkan bahwa ini masalahnya. Tingkat perbedaan yang tersedia dalam model ARIMA menghilangkan masalah tren musiman.
- Moving Average (MA): Rata-rata pergerakan membantu menghilangkan efek pergerakan acak harga alpukat dalam kasus kita. Jika ada peristiwa luar biasa yang menyebabkan lonjakan harga alpukat, rata-rata pergerakan akan membantu kita “memperhalus” segalanya dan model deret waktu kita tidak akan rentan terhadap fluktuasi ini.
forecast_cv <- forecast(arima_model_cv, h=24)
# Include means including the last 60 months in order to see closer the forecast.
autoplot(forecast_cv, include=60) + theme_minimal() + theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#F4F6F7"),
legend.position="bottom", legend.background = element_rect(fill="#FFF9F5",
size=0.5, linetype="solid",
colour ="black")) +
labs(title="Forecasting using ARIMA model \n Conventional Avocados", x="Date", y="Price")print(summary(forecast_cv))#>
#> Forecast method: ARIMA(0,1,1)(1,1,0)[12]
#>
#> Model Information:
#> Series: conv_ts
#> ARIMA(0,1,1)(1,1,0)[12]
#>
#> Coefficients:
#> ma1 sar1
#> -0.5852 -0.5753
#> s.e. 0.1547 0.1753
#>
#> sigma^2 = 0.05354: log likelihood = -0.42
#> AIC=6.84 AICc=7.93 BIC=10.61
#>
#> Error measures:
#> ME RMSE MAE MPE MAPE MASE
#> Training set -0.001571837 0.181512 0.1213104 -1.236511 9.331712 0.4580951
#> ACF1
#> Training set -0.04030292
#>
#> Forecasts:
#> Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
#> Apr 2018 1.0988344 0.8023047 1.395364 0.64533132 1.552337
#> May 2018 1.1386782 0.8176499 1.459706 0.64770779 1.629649
#> Jun 2018 1.4894879 1.1457024 1.833273 0.96371337 2.015262
#> Jul 2018 1.4012635 1.0361364 1.766391 0.84284987 1.959677
#> Aug 2018 1.4230391 1.0377508 1.808327 0.83379152 2.012287
#> Sep 2018 1.4977123 1.0932666 1.902158 0.87916596 2.116259
#> Oct 2018 1.5082521 1.0855162 1.930988 0.86173336 2.154771
#> Nov 2018 1.5475561 1.1072892 1.987823 0.87422604 2.220886
#> Dec 2018 1.3635789 0.9064528 1.820705 0.66446492 2.062693
#> Jan 2019 1.2473999 0.7740148 1.720785 0.52341981 1.971380
#> Feb 2019 1.2164340 0.7273300 1.705538 0.46841391 1.964454
#> Mar 2019 1.0593317 0.5549984 1.563665 0.28802046 1.830643
#> Apr 2019 1.3204560 0.7580290 1.882883 0.46029805 2.180614
#> May 2019 1.2510857 0.6619899 1.840182 0.35014138 2.152030
#> Jun 2019 1.5323955 0.9177870 2.147004 0.59243296 2.472358
#> Jul 2019 1.4143854 0.7752819 2.053489 0.43696101 2.391810
#> Aug 2019 1.4063753 0.7436816 2.069069 0.39287280 2.419878
#> Sep 2019 1.5704056 0.8849331 2.255878 0.52206585 2.618745
#> Oct 2019 1.5346121 0.8270937 2.242130 0.45255616 2.616668
#> Nov 2019 1.5110352 0.7821375 2.239933 0.39628237 2.625788
#> Dec 2019 1.3005818 0.5509142 2.050249 0.15406412 2.447099
#> Jan 2020 1.1016649 0.3317875 1.871542 -0.07576099 2.279091
#> Feb 2020 1.0309847 0.2414147 1.820555 -0.17655844 2.238528
#> Mar 2020 0.9930251 0.1842418 1.801808 -0.24390228 2.229953
forecast_org <- forecast(arima_model_or, h=24)
# Include means including the last 60 months in order to see closer the forecast.
autoplot(forecast_org, include=60) + theme_minimal() + theme(plot.title=element_text(hjust=0.5), plot.background=element_rect(fill="#d0f0c0"),
legend.position="bottom", legend.background = element_rect(fill="#fffacd",
size=0.5, linetype="solid",
colour ="black")) +
labs(title="Forecasting using ARIMA model \n Organic Avocados", x="Date", y="Price")print(summary(forecast_org))#>
#> Forecast method: ARIMA(0,1,0)(1,1,0)[12]
#>
#> Model Information:
#> Series: org_ts
#> ARIMA(0,1,0)(1,1,0)[12]
#>
#> Coefficients:
#> sar1
#> -0.6507
#> s.e. 0.1612
#>
#> sigma^2 = 0.03635: log likelihood = 3.41
#> AIC=-2.81 AICc=-2.29 BIC=-0.29
#>
#> Error measures:
#> ME RMSE MAE MPE MAPE MASE
#> Training set -0.00997634 0.1526389 0.09720952 -0.9593879 5.739336 0.3899936
#> ACF1
#> Training set -0.1943216
#>
#> Forecasts:
#> Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
#> Apr 2018 1.252732 1.00840915 1.497054 0.879072602 1.626391
#> May 2018 1.371436 1.02591219 1.716961 0.843002687 1.899870
#> Jun 2018 1.113831 0.69065147 1.537010 0.466634001 1.761027
#> Jul 2018 1.227520 0.73887478 1.716165 0.480201688 1.974838
#> Aug 2018 1.366225 0.81990280 1.912546 0.530697491 2.201752
#> Sep 2018 1.412253 0.81378729 2.010718 0.496978748 2.327527
#> Oct 2018 1.269042 0.62262564 1.915459 0.280433304 2.257651
#> Nov 2018 1.367747 0.67669858 2.058796 0.310879576 2.424615
#> Dec 2018 1.372818 0.63985006 2.105785 0.251840414 2.493795
#> Jan 2019 1.342677 0.57006076 2.115292 0.161062680 2.524290
#> Feb 2019 1.238085 0.42775848 2.048411 -0.001202315 2.477372
#> Mar 2019 1.302198 0.45583954 2.148556 0.007804594 2.596591
#> Apr 2019 1.235828 0.32753338 2.144123 -0.153288939 2.624946
#> May 2019 1.231742 0.26547194 2.198012 -0.246040426 2.709524
#> Jun 2019 1.037649 0.01669071 2.058607 -0.523771769 2.599069
#> Jul 2019 1.337642 0.26478012 2.410504 -0.303158692 2.978443
#> Aug 2019 1.353555 0.23118728 2.475924 -0.362958569 3.070070
#> Sep 2019 1.382647 0.21286579 2.552428 -0.406378973 3.171673
#> Oct 2019 1.065835 -0.14951098 2.281181 -0.792876251 2.924546
#> Nov 2019 1.041748 -0.21751465 2.301011 -0.884128235 2.967625
#> Dec 2019 1.004477 -0.29722214 2.306176 -0.986300122 2.995254
#> Jan 2020 1.059020 -0.28377550 2.401815 -0.994608289 3.112648
#> Feb 2020 1.009472 -0.37319780 2.392142 -1.105139090 3.124084
#> Mar 2020 1.005838 -0.41558851 2.427265 -1.168046383 3.179723
Conclusion
Ketika sampai pada analisis yang telah kita terapkan, ada beberapa pengamatan yang kita amati:
- Expensive Organic Avocados: Seperti yang diharapkan, kita telah memperhatikan bahwa alpukat organik jauh lebih mahal daripada alpukat konvensional.
- Similar patterns by type: Ada beberapa pola yang dapat dibedakan antara kedua jenis alpukat, namun kita melihat bahwa sebagian besar pola serupa antara kedua jenis alpukat ini.
- Tahun 2017: Tahun 2017 adalah tahun terbaik alpukat. Salah satu faktor yang dapat berkontribusi adalah kesejahteraan ekonomi secara keseluruhan tetapi mungkin juga ada faktor tersembunyi lainnya yang dapat mempengaruhi harga alpukat di pasar.
- Volatility: Meskipun 2017 adalah tahun dengan harga tertinggi, itu juga merupakan tahun dengan volatilitas tertinggi. Artinya, fluktuasi harga tahun ini lebih tinggi dibandingkan dua tahun terakhir.
- Buy avocados before fall!: kita melihat pola harga alpukat yang konsisten meningkat saat musim gugur datang. Ini berlaku untuk alpukat konvensional dan organik.
- Downward trend in the longrun: Berdasarkan model ARIMA kita, untuk kedua jenis alpukat, kita memperkirakan tren penurunan harga setidaknya dalam jangka panjang. Mungkin ada beberapa tekanan ke atas dalam jangka pendek. Namun, model kita memprediksi bahwa kedua harga alpukat akan turun 2 tahun dari sekarang.