Latar Belakang Masalah Bawang merah merupakan komoditas hortikultura strategis di Indonesia yang memiliki volatilitas harga tinggi akibat pola produksi yang bersifat musiman. Tantangan utama dalam menganalisis harga nasional adalah adanya disparitas (kesenjangan) harga yang ekstrem antarwilayah; harga di wilayah sentra produksi (seperti Jawa) jauh lebih rendah dibandingkan wilayah timur Indonesia (seperti Papua).
Analisis pengelompokan (clustering) konvensional yang menggunakan jarak Euclidean sering kali gagal menangkap kesamaan pola pada kondisi ini. Metode tersebut cenderung sensitif terhadap perbedaan skala (nominal harga) dan pencilan (outlier), sehingga provinsi dengan pola fluktuasi yang sama namun berbeda tingkat harga sering kali dianggap tidak mirip.
Solusi Penyelesaian Untuk mengatasi masalah tersebut, penelitian ini menerapkan metode Time Series Clustering dengan menggunakan ukuran jarak Mahalanobis Distance-Based Dynamic Time Warping (MDDTW).
Metode MDDTW dipilih karena menggabungkan keunggulan Dynamic Time Warping (DTW) dalam menangkap pergeseran pola waktu musiman, dengan ketangguhan jarak Mahalanobis yang mampu menstandarisasi perbedaan skala harga dan memperhitungkan korelasi antar data. Dengan pendekatan ini, pengelompokan 34 provinsi dilakukan berdasarkan kemiripan perilaku pola pergerakan harga, bukan sekadar nominal harganya, sehingga menghasilkan zonasi harga yang lebih akurat dan representatif untuk pengambilan kebijakan.
Import data dari file CSV ke R
data_tpg <- read.csv("C:/Users/nyayu/Statistika/Semester 5/TPG (Teknik Peubah Ganda)/data tugas.csv", header = TRUE, sep = ";")Melihat struktur data awal.
## 'data.frame': 340620 obs. of 9 variables:
## $ KabKot : chr "Kab. Aceh Barat" "Kab. Aceh Barat" "Kab. Aceh Barat" "Kab. Aceh Barat" ...
## $ Tahun : int 2022 2022 2022 2022 2022 2022 2022 2022 2022 2022 ...
## $ Bulan : chr "Januari" "Januari" "Januari" "Januari" ...
## $ Produk : chr "Beras Premium" "Beras Medium" "Bawang Merah" "Bawang Putih (Bonggol)" ...
## $ Harga : num 11429 9979 31000 28636 19409 ...
## $ Kategori: chr "Beras" "Beras" "Bawang" "Bawang" ...
## $ KodeBPS : num 11.1 11.1 11.1 11.1 11.1 ...
## $ KodeProv: int 11 11 11 11 11 11 11 11 11 11 ...
## $ NamaProv: chr "Aceh" "Aceh" "Aceh" "Aceh" ...
## [1] 340620 9
Data awal memiliki 9 kolom dan 340620 baris, 9 kolom tersebut adalah KabKot, Tahun, Bulan, Produk, Harga, Kategori, KodeBPS, KodeProv, dan NamaProv.
Selanjutnya, kita akan memeriksa keberadaan nilai NA (kosong) pada data.
## [1] TRUE
## KabKot Tahun Bulan Produk Harga Kategori KodeBPS KodeProv
## 0 0 0 0 61513 0 0 0
## NamaProv
## 3450
## [1] 64963
Data awal memiliki 2 kolom yang mengandung NA di antaranya yaitu kolom Harga dan kolom NamaProv. Dengan kolom Harga memiliki 61513 baris dan NamaProv 3450 baris.
Nilai yang hilang pada kolom Nama Provinsi hanya terjadi untuk Kode Provinsi 65. Setelah dicek berdasarkan nama kabupaten/kota, wilayah tersebut berada di Kalimantan Utara. Karena hanya ada satu provinsi dengan kode tersebut dan datanya yang hilang, maka Nama Provinsi yang benar untuk kode 65 adalah Kalimantan Utara.
data_tpg <- data_tpg %>%
mutate(NamaProv = ifelse(KodeProv == 65 & is.na(NamaProv), "Kalimantan Utara", NamaProv))
sum(is.na(data_tpg$NamaProv))## [1] 0
Selanjutnya dilakukan filter data untuk hanya menampilkan produk bawang merah.
## 'data.frame': 22708 obs. of 9 variables:
## $ KabKot : chr "Kab. Aceh Barat" "Kab. Aceh Barat" "Kab. Aceh Barat" "Kab. Aceh Barat" ...
## $ Tahun : int 2022 2022 2022 2022 2022 2022 2022 2022 2022 2022 ...
## $ Bulan : chr "Januari" "Februari" "Maret" "April" ...
## $ Produk : chr "Bawang Merah" "Bawang Merah" "Bawang Merah" "Bawang Merah" ...
## $ Harga : num 31000 33107 37600 35800 41600 ...
## $ Kategori: chr "Bawang" "Bawang" "Bawang" "Bawang" ...
## $ KodeBPS : num 11.1 11.1 11.1 11.1 11.1 ...
## $ KodeProv: int 11 11 11 11 11 11 11 11 11 11 ...
## $ NamaProv: chr "Aceh" "Aceh" "Aceh" "Aceh" ...
## [1] 22708 9
## KabKot Tahun Bulan Produk
## Length:22708 Min. :2022 Length:22708 Length:22708
## Class :character 1st Qu.:2022 Class :character Class :character
## Mode :character Median :2023 Mode :character Mode :character
## Mean :2023
## 3rd Qu.:2024
## Max. :2025
##
## Harga Kategori KodeBPS KodeProv
## Min. : 10000 Length:22708 Min. :11.01 Min. :11.0
## 1st Qu.: 30370 Class :character 1st Qu.:17.09 1st Qu.:17.0
## Median : 36208 Mode :character Median :35.21 Median :35.0
## Mean : 37651 Mean :43.81 Mean :43.6
## 3rd Qu.: 43470 3rd Qu.:71.01 3rd Qu.:71.0
## Max. :113148 Max. :92.71 Max. :92.0
## NA's :40
## NamaProv
## Length:22708
## Class :character
## Mode :character
##
##
##
##
Data bawang merah memiliki 22708 baris dan 9 kolom, dengan kolom Harga terdapat NA. Selanjutnya, kita akan mengurutkan data berdasarkan kota dan tanggal agar lebih mudah dianalisis.
data_bawang_sorted <- data_bawang_raw %>%
mutate(
BulanAngka = match(Bulan, c("Januari", "Februari", "Maret", "April", "Mei", "Juni",
"Juli", "Agustus", "September", "Oktober", "November", "Desember")),
Tanggal = as.Date(paste0(Tahun, "-", BulanAngka, "-01"))
) %>%
arrange(KabKot, Tanggal)Dilakukan pengecekan NA pada data bawang merah
cek_na_bawang <- data_tpg %>%
filter(Produk == "Bawang Merah", is.na(Harga)) %>%
select(NamaProv, KabKot, Tahun, Bulan) %>%
arrange(NamaProv, KabKot, Tahun)
print(cek_na_bawang)## NamaProv KabKot Tahun Bulan
## 1 Papua Kab. Teluk Bintuni 2025 Januari
## 2 Papua Kab. Teluk Bintuni 2025 Mei
## 3 Papua Kab. Teluk Bintuni 2025 Juni
## 4 Papua Barat Kab. Dogiyai 2025 Maret
## 5 Papua Barat Kab. Intan Jaya 2024 Oktober
## 6 Papua Barat Kab. Intan Jaya 2024 November
## 7 Papua Barat Kab. Intan Jaya 2024 Desember
## 8 Papua Barat Kab. Intan Jaya 2025 Januari
## 9 Papua Barat Kab. Intan Jaya 2025 Februari
## 10 Papua Barat Kab. Intan Jaya 2025 Maret
## 11 Papua Barat Kab. Intan Jaya 2025 April
## 12 Papua Barat Kab. Intan Jaya 2025 Mei
## 13 Papua Barat Kab. Intan Jaya 2025 Juni
## 14 Papua Barat Kab. Intan Jaya 2025 Juli
## 15 Papua Barat Kab. Jayawijaya 2022 Januari
## 16 Papua Barat Kab. Jayawijaya 2022 Februari
## 17 Papua Barat Kab. Jayawijaya 2022 Maret
## 18 Papua Barat Kab. Jayawijaya 2022 April
## 19 Papua Barat Kab. Jayawijaya 2022 Mei
## 20 Papua Barat Kab. Jayawijaya 2025 Maret
## 21 Papua Barat Kab. Mappi 2022 April
## 22 Papua Barat Kab. Mappi 2022 Mei
## 23 Papua Barat Kab. Puncak Jaya 2022 Januari
## 24 Papua Barat Kab. Puncak Jaya 2024 September
## 25 Papua Barat Kab. Puncak Jaya 2024 Desember
## 26 Papua Barat Kab. Puncak Jaya 2025 April
## 27 Papua Barat Kab. Yahukimo 2024 Maret
## 28 Papua Barat Kab. Yahukimo 2024 April
## 29 Sulawesi Selatan Kab. Bantaeng 2023 Maret
## 30 Sulawesi Selatan Kab. Bantaeng 2023 April
## 31 Sulawesi Selatan Kab. Bantaeng 2023 Mei
## 32 Sulawesi Selatan Kab. Bantaeng 2023 Juni
## 33 Sulawesi Selatan Kab. Bantaeng 2023 Juli
## 34 Sulawesi Selatan Kab. Bantaeng 2023 Agustus
## 35 Sulawesi Selatan Kab. Bantaeng 2023 September
## 36 Sulawesi Selatan Kab. Bantaeng 2023 Oktober
## 37 Sulawesi Selatan Kab. Bantaeng 2023 November
## 38 Sulawesi Selatan Kab. Bantaeng 2023 Desember
## 39 Sulawesi Selatan Kab. Bantaeng 2024 Januari
## 40 Sulawesi Selatan Kab. Bantaeng 2024 Februari
library(dplyr)
kekuatan_data <- data_bawang_raw %>%
group_by(NamaProv) %>%
summarise(
Jumlah_Kota = n_distinct(KabKot),
Kota_Bermasalah = sum(unique(KabKot) %in% c("Kab. Teluk Bintuni", "Kab. Dogiyai", "Kab. Intan Jaya", "Kab. Jayawijaya", "Kab. Mappi","Kab. Puncak Jaya","Kab. Yahukimo","Kab. Bantaeng" )),
.groups = "drop"
) %>%
arrange(Jumlah_Kota)
print("--- Jumlah Kota Penyusun Harga Provinsi ---")## [1] "--- Jumlah Kota Penyusun Harga Provinsi ---"
## # A tibble: 34 × 3
## NamaProv Jumlah_Kota Kota_Bermasalah
## <chr> <int> <int>
## 1 DI Yogyakarta 5 0
## 2 Kalimantan Utara 5 0
## 3 DKI Jakarta 6 0
## 4 Gorontalo 6 0
## 5 Kepulauan Bangka Belitung 7 0
## 6 Kepulauan Riau 7 0
## 7 Sulawesi Barat 7 0
## 8 Banten 8 0
## 9 Bali 9 0
## 10 Bengkulu 10 0
## # ℹ 24 more rows
filter_provinsi_masalah <- kekuatan_data %>%
filter(NamaProv %in% c("Sulawesi Selatan", "Papua", "Papua Barat"))
print("--- Detail Provinsi dengan Missing Value ---")## [1] "--- Detail Provinsi dengan Missing Value ---"
## # A tibble: 3 × 3
## NamaProv Jumlah_Kota Kota_Bermasalah
## <chr> <int> <int>
## 1 Papua 12 1
## 2 Papua Barat 21 6
## 3 Sulawesi Selatan 24 1
data_bawang_prov <- data_bawang_sorted %>%
group_by(NamaProv, Tanggal) %>%
summarise(
Harga_Mean = mean(Harga, na.rm = TRUE),
.groups = "drop"
)Dengan menggunakan group_by(NamaProv) dan mean(…, na.rm = TRUE), perhitungan rata-rata harga dilakukan secara terpisah untuk setiap provinsi hanya menggunakan data kota/kabupaten yang tersedia di provinsi tersebut. Jadi, jika di Provinsi Papua ada 5 kota tetapi 1 kota datanya kosong, maka rata-rata harga Provinsi Papua pada tanggal itu dihitung murni dari rata-rata 4 kota di Papua yang datanya ada, tanpa terpengaruh oleh data dari provinsi lain atau kota yang kosong tersebut.
library(dplyr)
library(tidyr)
library(ggplot2)
data_cek <- data_bawang_prov %>%
filter(NamaProv %in% c("Sulawesi Selatan", "Jawa Timur"))
ggplot(data_cek, aes(x = Tanggal, y = Harga_Mean, color = NamaProv)) +
geom_line(linewidth = 1) +
geom_point() +
scale_x_date(date_breaks = "3 months", date_labels = "%b %Y") +
theme_minimal() +
labs(
title = "Uji Validitas Pola Time Series",
subtitle = "Perhatikan garis Sulawesi Selatan: Apakah mulus atau terputus?",
y = "Harga Rata-rata (Rp)",
x = "Waktu"
) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Pada pola tersebut dibandingkan hasil time series antara Sulawesi Selatan (yang memiliki data NA sebelumnya) dengan Jawa Timur (yang tidak memiliki data NA). Terlihat bahwa pola garis Sulawesi Selatan tetap mulus tanpa ada putus, menandakan bahwa pengisian NA dengan rata-rata provinsi berhasil mempertahankan pola pergerakan harga yang valid.
## Jumlah NA Final di Level Provinsi: 0
library(ggplot2)
library(dplyr)
ggplot(data_bawang_prov, aes(x = Tanggal, y = Harga_Mean, group = NamaProv, color = NamaProv)) +
geom_line(alpha = 0.7, linewidth = 0.8) +
scale_x_date(date_breaks = "4 months", date_labels = "%b %Y") + # Label per 4 bulan
theme_minimal() +
labs(
title = "Tren Harga Bawang Merah Seluruh Provinsi",
subtitle = "Pola Pergerakan Harga Bulanan (2022-2025)",
y = "Harga Rata-rata (Rp)",
x = "Periode Waktu",
color = "Provinsi"
) +
theme(
legend.position = "right",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 7),
axis.text.x = element_text(angle = 45, hjust = 1)
)Meskipun secara umum harga bawang merah mengalami fluktuasi musiman di seluruh Indonesia, visualisasi data memperlihatkan adanya dualitas pola yang mencolok. Mayoritas provinsi menunjukkan pergerakan harga yang relatif lebih terkendali dan seragam di kisaran harga rendah, membentuk pola gerombolan yang rapat di bagian bawah grafik. Kondisi ini sangat kontras dengan segelintir provinsi outlier (terutama di wilayah timur) yang memisahkan diri dengan tren harga jauh lebih tinggi dan mengalami gejolak volatilitas ekstrem, ditandai dengan lonjakan tajam yang tidak dialami oleh provinsi lain. Disparitas perilaku harga ini—antara kelompok yang pola fluktuasinya moderat dan kelompok yang sangat fluktuatif—menegaskan bahwa pendekatan rata-rata nasional akan bias, sehingga mutlak diperlukan metode clustering untuk memisahkan dan menangani karakteristik yang bertolak belakang tersebut.
ggplot(data_bawang_prov, aes(x = reorder(NamaProv, Harga_Mean), y = Harga_Mean, fill = NamaProv)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 8, show.legend = FALSE) +
coord_flip() +
theme_minimal() +
labs(
title = "Distribusi Harga Bawang Merah Antar Provinsi",
subtitle = "Diurutkan dari Median Harga Terendah ke Tertinggi",
y = "Sebaran Harga (Rp)",
x = "Provinsi"
) +
theme(
axis.text.y = element_text(size = 8)
)Visualisasi distribusi harga mempertegas adanya disparitas struktural yang ekstrem antarwilayah, di mana sentra produksi utama (seperti NTB dan Jambi) konsisten menempati posisi terendah dengan variabilitas harga yang relatif stabil, sangat kontras dengan wilayah timur (seperti Papua dan Maluku Utara) yang memiliki median harga tertinggi disertai rentang volatilitas yang lebar akibat ketidakpastian rantai pasok . Meskipun keberadaan pencilan (outlier) yang tersebar di hampir seluruh provinsi mengonfirmasi bahwa kerentanan terhadap guncangan harga musiman adalah fenomena nasional, perbedaan fundamental profil risiko antara wilayah “stabil-murah” dan “fluktuatif-mahal” ini menjadi bukti empiris kuat bahwa analisis berbasis rata-rata nasional akan bias, sehingga memvalidasi urgensi penerapan metode clustering untuk memisahkan strategi penanganan berdasarkan karakteristik unik setiap wilayah.
Data bawang merah bentuknya memanjang ke bawah (satu baris = satu tanggal per provinsi). Algoritma clustering dan perhitungan jarak (seperti MDDTW) membutuhkan input di mana satu baris mewakili satu objek utuh (dalam hal ini, satu deret waktu penuh untuk satu provinsi). Hasilnya: Kolom Tanggal akan “disebar” menjadi nama-nama kolom baru, dan isi tabelnya adalah Harga_Mean.
Setelah itu kita mengubah dataframe menjadi matriks numerik.
## [1] 34 46
Sesuai konsep, MDDTW menggabungkan jarak Mahalanobis (untuk mengatasi perbedaan skala/varians) dengan DTW (untuk mengatasi pergeseran waktu).
Pada langkah ini, kita menyiapkan parameter “pembobot” untuk perhitungan jarak Mahalanobis. Secara sederhana, kode ini menghitung seberapa “liar” atau menyebar data harga bawang merah di seluruh Indonesia, supaya nanti saat kita membandingkan harga antarprovinsi, perbandingannya menjadi adil (terstandarisasi).
var(as.vector(data_matrix)): Kita mengambil seluruh angka harga dari semua provinsi dan semua bulan, lalu menghitung Varians Global-nya. Ini mengukur seberapa jauh harga-harga tersebut menyebar dari rata-rata nasional.
inv_covariance <- 1 / global_variance: Ini adalah Invers Matriks Kovarian (\(S^{-1}\)) versi sederhana. Karena data kita hanya satu jenis (hanya “Harga”, disebut univariat), matriks kovarian berubah menjadi satu angka saja yaitu Varians.Invers-nya berarti \(1\) dibagi Varians (\(1/s^2\)).
global_variance <- var(as.vector(data_matrix), na.rm = TRUE)
inv_covariance <- 1 / global_variance
global_sd <- sqrt(global_variance)
print(paste("Global SD:", global_sd))## [1] "Global SD: 10078.4703309907"
## [1] "Global Variance: 101575564.212659"
Global SD artinya secara rata-rata, harga bawang merah di Indonesia menyimpang (naik atau turun) sekitar Rp 10.078 dari harga rata-rata nasional. Ini angka yang sangat masuk akal mengingat harga bawang bisa bergerak dari Rp 20.000 ke Rp 40.000-an.
Dalam rumus MDDTW yang digunakan, jarak Mahalanobis dihitung dengan melibatkan matriks kovarian invers (\(S^{-1}\)). Karena data Anda hanya satu variabel (univariat), matriks kovarian \(S\) itu sama dengan Global Variance ini. Nantinya, algoritma akan menggunakan angka ini sebagai pembagi.
\[ \text{Jarak MDDTW} = \frac{\text{Selisih harga}}{\text{Global SD}} \]
Selanjutnya, fungsi di bawah ini bertugas menghitung jarak kemiripan antara dua deret waktu (misal: Provinsi Aceh vs. Sumatera Utara) dengan mempertimbangkan varians data global. Fungsi calc_mddtw ini adalah modifikasi algoritma DTW standar. Jika DTW biasa langsung menghitung jarak Euclidean mentah, fungsi ini membangun Matriks Biaya Lokal secara manual terlebih dahulu menggunakan rumus jarak Mahalanobis. Dengan cara ini, algoritma tidak hanya ‘mencocokkan bentuk’ grafik harga antarprovinsi, tetapi juga menstandarisasi skala harganya berdasarkan varians global data. Hasilnya adalah ukuran kemiripan yang murni berdasarkan pola fluktuasi, dan tidak bias oleh perbedaan nominal harga yang ekstrem antara wilayah barat dan timur Indonesia.
dengan langkah-langkah:
calc_mddtw <- function(ts1, ts2, inv_cov) {
ts1 <- as.numeric(ts1)
ts2 <- as.numeric(ts2)
if(any(is.na(ts1)) | any(is.na(ts2))) return(NA)
scaling_factor <- sqrt(inv_cov)
local_cost_matrix <- abs(outer(ts1, ts2, "-")) * scaling_factor
alignment <- dtw(local_cost_matrix, distance.only = TRUE)
return(alignment$distance)
}Kode di bawah ini bertujuan untuk membangun Matriks Jarak (Distance Matrix) berukuran \(34 \times 34\). Matriks ini berisi nilai ketidakmiripan (dissimilarity) antara satu provinsi dengan provinsi lainnya. Jika nilainya kecil: Pola harga kedua provinsi sangat mirip. Jika nilainya besar: Pola harga kedua provinsi sangat berbeda.
Pada tahap ini, dilakukan komputasi iteratif untuk menyusun Matriks Jarak Simetris antar 34 provinsi. Algoritma membandingkan pola harga setiap pasangan provinsi (total kombinasi \(_{34}C_2\)) menggunakan fungsi MDDTW yang telah didefinisikan.
Hasil akhirnya adalah sebuah matriks yang merepresentasikan ‘peta kedekatan’ pola harga. Sebagai contoh, jika jarak antara Jawa Tengah dan Jawa Timur kecil, berarti kedua provinsi ini memiliki perilaku harga yang serupa dan berpotensi masuk dalam gerombol yang sama. Sebaliknya, jika jarak antara Jawa Tengah dan Papua besar, berarti pola fluktuasi harganya sangat berbeda.
dengan langkah-langkah:
Ambil deret waktu harga provinsi ke-\(i\) (misal: Aceh).
Ambil deret waktu harga provinsi ke-\(j\) (misal: Papua).
calc_mddtw(...): Masukkan kedua
deret waktu tersebut ke dalam fungsi MDDTW yang sudah kita buat
sebelumnya. Fungsi ini akan mengembalikan satu angka
(skor jarak).
Penyimpanan: Masukkan skor jarak tersebut ke dalam matriks di posisi \((i, j)\) dan \((j, i)\) agar matriksnya simetris (cerminan).
hclust()
(Hierarchical Clustering) di R membutuhkan input berupa objek khusus
bernama dist, bukan matriks biasa. Perintah ini mengubah
format matriks kotak tadi menjadi format segitiga bawah yang lebih hemat
memori dan siap untuk di-cluster.n_prov <- nrow(data_matrix)
dist_matrix <- matrix(0, nrow = n_prov, ncol = n_prov)
rownames(dist_matrix) <- rownames(data_matrix)
colnames(dist_matrix) <- rownames(data_matrix)
# Looping (Perulangan) Perhitungan
for(i in 1:(n_prov-1)) {
for(j in (i+1):n_prov) {
ts_i <- data_matrix[i, ]
ts_j <- data_matrix[j, ]
d_val <- calc_mddtw(ts_i, ts_j, inv_covariance)
dist_matrix[i, j] <- d_val
dist_matrix[j, i] <- d_val
}
}
# Ubah menjadi objek 'dist' untuk clustering
dist_object <- as.dist(dist_matrix)
# Cek hasil (tampilkan 5x5 pertama agar laporan tidak terlalu panjang)
print(as.matrix(dist_object)[1:5, 1:5])## Aceh Bali Banten Bengkulu DI Yogyakarta
## Aceh 0.00000 25.79051 16.77682 11.94604 20.78226
## Bali 25.79051 0.00000 26.68291 25.45134 20.11716
## Banten 16.77682 26.68291 0.00000 14.31027 16.63738
## Bengkulu 11.94604 25.45134 14.31027 0.00000 19.93489
## DI Yogyakarta 20.78226 20.11716 16.63738 19.93489 0.00000
Output di atas adalah Matriks Ketidakmiripan (Dissimilarity Matrix) antarprovinsi berdasarkan pola harga bawang merah. Angka-angka tersebut merepresentasikan jarak MDDTW; semakin kecil angkanya, semakin mirip pola pergerakan harganya. Sebaliknya, semakin besar angkanya, semakin berbeda polanya. Diagonal Utama Bernilai 0 Nilai \(0.00000\) pada diagonal (Aceh-Aceh, Bali-Bali, dst.) menunjukkan jarak antara provinsi dengan dirinya sendiri.
Matriks jarak menunjukkan variasi nilai ketidakmiripan yang logis. Provinsi dengan kedekatan geografis seperti Aceh dan Bengkulu memiliki jarak MDDTW yang rendah (11.95), mengindikasikan sinkronisasi pola harga yang kuat. Sebaliknya, provinsi yang terpisah jauh secara logistik seperti Banten dan Bali menunjukkan jarak yang lebih besar (26.68), merefleksikan dinamika pasar yang berbeda.
Tahap ini merupakan validasi internal untuk menentukan metode pautan terbaik. Berdasarkan nilai Koefisien Korelasi Cophenetic tertinggi.
Tujuan: Kita mendaftarkan 5 algoritma penggerombolan yang akan diuji.
Single: Menggabungkan berdasarkan tetangga terdekat.
Complete: Menggabungkan berdasarkan tetangga terjauh.
Average (Rataan): Menggabungkan berdasarkan rata-rata jarak seluruh anggota.
Centroid: Menggabungkan berdasarkan pusat gerombol.
Ward: Menggabungkan dengan meminimalkan varians dalam gerombol.
library(cluster)
library(fpc)
library(factoextra)
metode_pautan <- c("single", "complete", "average", "centroid", "ward.D2")
nama_tampilan <- c("Tunggal", "Lengkap", "Rataan", "Centroid", "Ward")
nilai_coph <- numeric(length(metode_pautan))
for(i in 1:length(metode_pautan)) {
hc_temp <- hclust(dist_object, method = metode_pautan[i])
coph_dist <- cophenetic(hc_temp)
nilai_coph[i] <- cor(dist_object, coph_dist)
}
hasil_evaluasi <- data.frame(Metode = nama_tampilan,
Kode_R = metode_pautan,
Cophenetic = nilai_coph)
print("=== HASIL EVALUASI PAUTAN ===")## [1] "=== HASIL EVALUASI PAUTAN ==="
## Metode Kode_R Cophenetic
## 3 Rataan average 0.8244066
## 2 Lengkap complete 0.8224773
## 4 Centroid centroid 0.8049175
## 5 Ward ward.D2 0.7884391
## 1 Tunggal single 0.7734681
best_index <- which.max(nilai_coph)
best_method_name <- metode_pautan[best_index]
best_method_label <- nama_tampilan[best_index]
cat("\nMetode Terbaik Terpilih:", best_method_label, "\n")##
## Metode Terbaik Terpilih: Rataan
## Menyimpan model terbaik ke 'hc_result'...
Sesuai dengan kriteria evaluasi kemiripan, metode dengan nilai koefisien yang paling mendekati 1 dianggap sebagai metode terbaik. Artinya, dendrogram yang dihasilkan oleh metode Average Linkage mampu merepresentasikan struktur jarak asli (MDDTW) antarprovinsi dengan tingkat akurasi paling tinggi dibandingkan empat metode lainnya. Metode Pautan Rataan (Average Linkage) menghasilkan nilai Koefisien Korelasi Cophenetic tertinggi, yaitu sebesar 0.8244066. Sehingga pautan rataan dipilih sebagai metode penggerombolan final.
Kode ini bertujuan untuk menentukan Jumlah Gerombol Optimal (\(k\)). Dalam Unsupervised Learning (seperti clustering), kita tidak tahu sejak awal berapa banyak kelompok yang sebaiknya dibentuk. Oleh karena itu, kita mencoba memotong pohon dendrogram dari \(k=2\) hingga \(k=10\), lalu menghitung skor kualitasnya menggunakan tiga metode statistik berbeda:
Silhouette Index: Mengukur seberapa pas suatu objek berada di gerombolnya sendiri dibandingkan gerombol tetangga.
Calinski-Harabasz Index (CHI): Mengukur rasio antara penyebaran antar-gerombol (separation) dengan kepadatan dalam-gerombol (compactness)
Elbow Method (WSS): Mengukur total varians dalam gerombol. Kita mencari titik di mana penurunan varians mulai melandai (seperti siku tangan).
k_range <- 2:10
metrics <- data.frame(k = k_range,
Silhouette = NA,
CHI = NA,
WSS = NA)
cat("\nMenghitung Indeks Validasi (Silhouette, CHI, Elbow)...\n")##
## Menghitung Indeks Validasi (Silhouette, CHI, Elbow)...
for(i in 1:length(k_range)) {
k <- k_range[i]
cluster_labels <- cutree(hc_result, k = k)
sil <- silhouette(cluster_labels, dist_object)
metrics$Silhouette[i] <- summary(sil)$avg.width
stats <- cluster.stats(dist_object, cluster_labels)
metrics$CHI[i] <- stats$ch
metrics$WSS[i] <- stats$within.cluster.ss
}
print(metrics)## k Silhouette CHI WSS
## 1 2 0.6391421 53.31532 11102.424
## 2 3 0.3721286 56.40828 6380.396
## 3 4 0.3425486 40.37126 5876.409
## 4 5 0.2570780 31.84684 5488.975
## 5 6 0.2598906 28.49491 4861.757
## 6 7 0.2128291 28.25164 4067.000
## 7 8 0.2254591 26.50400 3638.315
## 8 9 0.2186942 25.90586 3186.287
## 9 10 0.2009179 23.87112 2974.397
par(mfrow = c(1, 3))
plot(metrics$k, metrics$Silhouette, type = "b", pch = 19, col = "blue",
main = "Metode Silhouette", xlab = "Jumlah Gerombol (k)", ylab = "Avg Silhouette Width")
abline(v = metrics$k[which.max(metrics$Silhouette)], col = "red", lty = 2)
plot(metrics$k, metrics$CHI, type = "b", pch = 19, col = "green",
main = "Calinski-Harabasz Index", xlab = "Jumlah Gerombol (k)", ylab = "CHI Value")
abline(v = metrics$k[which.max(metrics$CHI)], col = "red", lty = 2)
plot(metrics$k, metrics$WSS, type = "b", pch = 19, col = "orange",
main = "Metode Elbow", xlab = "Jumlah Gerombol (k)", ylab = "Total WSS")Berdasarkan hasil ketiga metode validasi internal, jumlah gerombol optimal untuk data harga bawang merah yang digunakan adalah 3 gerombol. Meskipun metode Silhouette menyarankan 2 gerombol, namun metode Calinski-Harabasz dan Elbow Method keduanya sepakat pada 3 gerombol. Oleh karena itu, untuk analisis selanjutnya, kita akan menggunakan k=3 sebagai jumlah cluster final.
library(factoextra)
fviz_dend(hc_result,
k = 3,
cex = 0.6,
# --- PENGATURAN WARNA ---
# Urutan: Cluster 1 (Biru), Cluster 2 (Kuning), Cluster 3 (Merah)
k_colors = c("#2E9FDF", "#E7B800", "#FC4E07"),
color_labels_by_k = TRUE,
rect = TRUE,
rect_fill = TRUE, # Opsional: memberi warna latar belakang kotak transparan
main = "Dendrogram Penggerombolan Harga Bawang Merah",
xlab = "Provinsi", ylab = "Jarak MDDTW")
Dendrogram ini adalah visualisasi “pohon kekerabatan” harga bawang
merah, di mana tinggi rendahnya garis vertikal menunjukkan tingkat
kemiripan; semakin pendek garisnya, semakin “kembar” pola harganya,
sedangkan semakin tinggi garisnya, semakin berbeda karakteristik
pasarnya. Pembagian menjadi tiga warna (kluster) menegaskan bahwa
Indonesia terbagi menjadi tiga zona perilaku harga yang berbeda.
Percabangan dalam diagram ini menceritakan detail hubungan tersebut, di
mana provinsi yang menyatu di bagian bawah memiliki sinkronisasi harga
yang sangat kuat.
Secara spesifik pada kluster biru, Maluku Utara dan Papua menyatu lebih dulu karena keduanya memiliki pola lonjakan harga yang paling identik dan ekstrem, sedangkan Papua Barat memisah (bergabung di garis yang lebih tinggi) karena meskipun sama-sama mahal, ia memiliki sedikit perbedaan pola atau waktu lonjakan dibanding dua lainnya. Sementara itu, pada kluster kuning dan merah, percabangannya tampak jauh lebih pendek dan rapat-rapat, yang menandakan bahwa provinsi-provinsi di dalamnya (seperti Jawa, Sumatera, dan Kalimantan) memiliki pasar yang sangat terintegrasi dan kompak; artinya, jika harga berubah di satu provinsi, provinsi tetangganya akan langsung mengikuti dengan pola yang nyaris seragam tanpa perbedaan yang signifikan.
k_optimal <- 3
final_clusters <- cutree(hc_result, k = k_optimal)
df_clustered <- data.frame(NamaProv = rownames(data_matrix),
Cluster = final_clusters)
print(df_clustered %>% arrange(Cluster))## NamaProv Cluster
## Aceh Aceh 1
## Bali Bali 1
## Banten Banten 1
## Bengkulu Bengkulu 1
## DI Yogyakarta DI Yogyakarta 1
## Jambi Jambi 1
## Jawa Barat Jawa Barat 1
## Jawa Tengah Jawa Tengah 1
## Jawa Timur Jawa Timur 1
## Kalimantan Selatan Kalimantan Selatan 1
## Lampung Lampung 1
## Nusa Tenggara Barat Nusa Tenggara Barat 1
## Nusa Tenggara Timur Nusa Tenggara Timur 1
## Riau Riau 1
## Sulawesi Barat Sulawesi Barat 1
## Sulawesi Selatan Sulawesi Selatan 1
## Sumatera Barat Sumatera Barat 1
## Sumatera Selatan Sumatera Selatan 1
## Sumatera Utara Sumatera Utara 1
## DKI Jakarta DKI Jakarta 2
## Gorontalo Gorontalo 2
## Kalimantan Barat Kalimantan Barat 2
## Kalimantan Tengah Kalimantan Tengah 2
## Kalimantan Timur Kalimantan Timur 2
## Kalimantan Utara Kalimantan Utara 2
## Kepulauan Bangka Belitung Kepulauan Bangka Belitung 2
## Kepulauan Riau Kepulauan Riau 2
## Maluku Maluku 2
## Sulawesi Tengah Sulawesi Tengah 2
## Sulawesi Tenggara Sulawesi Tenggara 2
## Sulawesi Utara Sulawesi Utara 2
## Maluku Utara Maluku Utara 3
## Papua Papua 3
## Papua Barat Papua Barat 3
df_long_with_cluster <- data_bawang_prov %>%
left_join(df_clustered, by = "NamaProv")
prototypes <- df_long_with_cluster %>%
group_by(Cluster, Tanggal) %>%
summarise(Harga_Prototype = mean(Harga_Mean, na.rm = TRUE), .groups = 'drop')
library(ggplot2)
ggplot(prototypes, aes(x = as.Date(Tanggal), y = Harga_Prototype, color = as.factor(Cluster))) +
geom_line(size = 1.2) +
scale_color_manual(values = c("1" = "#E7B800", # Kuning (Gelap agar terlihat jelas)
"2" = "#FC4E07", # Merah
"3" = "#2E9FDF"), # Biru
labels = c("1 (Kuning)", "2 (Merah)", "3 (Biru)")) +
labs(title = "Prototype Deret Waktu Harga Bawang Merah per Gerombol",
x = "Periode", y = "Harga Rataan (Rp)", color = "Gerombol") +
theme_minimal() +
theme(legend.position = "bottom")
Hasil visualisasi prototipe deret waktu harga bawang merah menunjukkan
bahwa ketiga gerombol memiliki pola pergerakan harga yang serupa, tetapi
dengan tingkat harga rata-rata yang berbeda secara konsisten. Gerombol 3
(biru) terlihat memiliki harga rata-rata tertinggi sepanjang periode
2022–2025, menandakan bahwa provinsi-provinsi di dalam klaster ini
cenderung mengalami harga bawang merah yang lebih mahal. Kenaikan tajam
pada awal tahun 2022 terlihat paling jelas pada gerombol ini, kemudian
diikuti pola fluktuasi yang relatif stabil namun tetap berada pada
tingkat harga yang tinggi dibandingkan dua gerombol lainnya.
Gerombol 2 (merah) menunjukkan posisi harga menengah. Polanya cukup mirip dengan gerombol biru, termasuk lonjakan harga besar pada awal 2022 dan pertengahan 2024, tetapi tingkat harganya berada di bawah gerombol biru. Hal ini menunjukkan bahwa provinsi dalam gerombol merah menghadapi dinamika harga yang lebih moderat, kemungkinan dipengaruhi oleh kondisi pasokan yang lebih baik dibandingkan klaster 3, namun tetap rentan terhadap fluktuasi musiman dan perubahan distribusi antar wilayah. Secara umum, tren harga pada gerombol merah mengalami peningkatan bertahap hingga 2025.
Gerombol 1 (kuning) merupakan kelompok dengan harga rata-rata terendah di antara ketiganya. Meskipun mengalami lonjakan yang sama pada awal 2022 dan pertengahan 2024, level harga pada klaster ini tetap paling rendah dan cenderung mengalami fluktuasi lebih tajam pada beberapa titik waktu. Perilaku ini mengindikasikan bahwa provinsi dalam gerombol kuning memiliki kondisi pasar yang lebih kompetitif atau memiliki akses pasokan yang relatif lebih stabil sehingga harga berada pada tingkat yang lebih rendah. Secara keseluruhan, perbandingan ketiga klaster menegaskan adanya perbedaan struktural dalam pola harga antar wilayah, yang perlu dipertimbangkan dalam evaluasi kebijakan stabilisasi harga bawang merah.
library(ggplot2)
library(dplyr)
# --- KONFIGURASI WARNA IDENTITAS CLUSTER ---
# 1=Kuning, 2=Merah, 3=Biru (Sesuai request Anda)
warna_cluster <- c("1" = "#E7B800",
"2" = "#FC4E07",
"3" = "#2E9FDF")
# --- LOOPING UNTUK MEMBUAT 3 PLOT ---
for (k in 1:3) {
# 1. Filter Data Provinsi Anggota Cluster k
data_anggota <- df_long_with_cluster %>%
filter(Cluster == k)
# 2. Filter Data Prototype Cluster k (untuk garis tebal)
data_proto <- prototypes %>%
filter(Cluster == k)
# 3. Buat Plot
p <- ggplot() +
# Layer 1: Garis Tipis (Setiap Provinsi Anggota)
# Diberi warna beda-beda tiap provinsi agar bisa dibedakan
geom_line(data = data_anggota,
aes(x = as.Date(Tanggal), y = Harga_Mean, group = NamaProv, color = NamaProv),
size = 0.7, alpha = 0.7) +
# Layer 2: Garis Tebal (Prototype/Rataan Cluster)
# Warnanya disesuaikan dengan identitas cluster (Kuning/Merah/Biru)
geom_line(data = data_proto,
aes(x = as.Date(Tanggal), y = Harga_Prototype),
color = warna_cluster[as.character(k)], # Ambil warna dari konfigurasi di atas
size = 1.5, linetype = "dashed") + # Putus-putus agar beda
# Label dan Judul
labs(title = paste("Dinamika Harga Anggota Gerombol", k),
subtitle = paste("Garis Putus-putus = Rataan (Prototype) Cluster", k),
x = "Periode",
y = "Harga (Rp/kg)",
color = "Provinsi Anggota") +
theme_minimal() +
theme(legend.position = "right", # Legenda provinsi di kanan
plot.title = element_text(face = "bold", size = 14))
# Tampilkan Plot
print(p)
}
Gambar dinamika harga untuk Gerombol 1 menunjukkan bahwa seluruh
provinsi dalam klaster ini memiliki pola pergerakan harga yang sangat
mirip sepanjang periode 2022–2025. Meskipun terdapat variasi
antarprovinsi, keseluruhan anggota klaster cenderung mengikuti
naik-turunnya harga pada waktu yang sama. Hal ini terlihat dari
pergerakan garis yang saling berdekatan dan bergerak paralel, terutama
ketika terjadi lonjakan harga besar pada awal 2022 serta kenaikan
kembali pada pertengahan 2024.
Secara umum, provinsi dalam Gerombol 1 merupakan kelompok dengan tingkat harga bawang merah terendah dibandingkan klaster lainnya, sesuai dengan prototipe (garis putus-putus). Meskipun harganya relatif lebih rendah, fluktuasi musiman tetap terlihat jelas, terutama pada periode panen raya dan periode ketika pasokan menurun. Setelah penurunan harga yang cukup tajam pada awal 2023 dan awal 2024, sebagian besar provinsi di klaster ini kembali menunjukkan pola kenaikan harga yang konsisten menuju 2025.
Pola yang seragam ini mengindikasikan bahwa seluruh provinsi di Gerombol 1 kemungkinan memiliki struktur pasar dan sumber pasokan yang relatif mirip, sehingga respon harga terhadap perubahan pasokan dan permintaan juga bergerak serentak. Kemiripan ini memperkuat alasan pengelompokan mereka ke dalam satu klaster, karena dinamika harga antarprovinsi di dalamnya cenderung berjalan searah dan berada pada kisaran harga yang sama.
Gambar dinamika harga untuk Gerombol 2 menunjukkan bahwa provinsi-provinsi dalam klaster ini memiliki pola pergerakan harga yang seragam, meskipun tingkat harga antarprovinsi tidak selalu identik. Secara umum, seluruh anggota mengalami puncak harga yang sama pada awal tahun 2022 dan kembali menampilkan pola fluktuasi yang hampir paralel hingga tahun 2025. Hal ini terlihat dari garis-garis yang bergerak naik dan turun pada waktu yang sama, menandakan adanya pengaruh musiman atau faktor pasokan yang dirasakan serentak oleh provinsi dalam klaster ini.
Jika dibandingkan dengan Gerombol 1, tingkat harga pada Gerombol 2 berada pada kisaran menengah, sesuai dengan nilai prototipe (ditunjukkan oleh garis putus-putus). Meskipun begitu, beberapa provinsi seperti Maluku dan Kalimantan Timur kadang berada sedikit di atas rataannya, sementara provinsi seperti Gorontalo atau Kepulauan Bangka Belitung berada sedikit di bawah. Perbedaan kecil ini masih dalam rentang wajar dan tidak mengubah karakter umum bahwa seluruh provinsi dalam klaster ini mengikuti pola dinamika harga yang hampir sama.
Kemiripan pola yang kuat menunjukkan bahwa provinsi dalam Gerombol 2 memiliki struktur pasar, pola permintaan, dan keterhubungan distribusi yang relatif searah. Fluktuasi besar pada awal 2024 dan 2025 juga terlihat bergerak serempak di semua provinsi, menandakan bahwa klaster ini cukup sensitif terhadap perubahan pasokan nasional. Dengan demikian, karakteristik Gerombol 2 dapat dipahami sebagai kelompok provinsi dengan harga menengah dan respons harga yang bergerak bersama terhadap kejadian pasar yang sama.
Gambar dinamika harga untuk Gerombol 3 justru memperlihatkan ketidakseragaman (heterogenitas) yang paling tinggi dibandingkan gerombol lainnya. Berbeda dengan gerombol stabil yang garis-garisnya saling menempel, garis-garis harga provinsi pada Gerombol 3 terlihat menyebar lebar dan “berantakan”, terutama saat terjadi kenaikan harga. Jarak (gap) antara provinsi dengan harga tertinggi dan terendah dalam satu waktu sangat jauh; ada provinsi yang harganya sudah tembus Rp 100.000 (puncak tertinggi), sementara anggota lain di waktu yang sama mungkin masih di Rp 60.000.
Secara karakteristik, ini adalah Gerombol Volatilitas Ekstrem. Garis rataan (prototype) putus-putus berada di tengah, namun garis-garis asli anggotanya bergerak liar di atas dan di bawahnya. Hal ini menandakan bahwa meskipun provinsi-provinsi ini sama-sama memiliki tren harga mahal (rata-rata tinggi), perilaku pasarnya tidak terintegrasi dengan baik. Kenaikan harga di satu provinsi tidak selalu diikuti dengan irama yang sama oleh provinsi lain dalam gerombol ini.
Pola yang “renggang” dan penuh lonjakan tajam ini sangat khas merepresentasikan Wilayah Defisit/Timur (seperti Papua, Maluku). Keregangan garis-garis ini mengindikasikan bahwa setiap provinsi seolah memiliki masalah logistiknya masing-masing yang unik—satu daerah mungkin telat kapal sehingga harga meroket sendirian, sementara daerah tetangganya aman. Ini adalah bukti visual dari inefisiensi pasar dan tingginya risiko ketidakpastian harga di wilayah tersebut.
Visualisasi dinamika harga dari ketiga gerombol memberikan bukti empiris yang kuat bahwa pasar bawang merah di Indonesia terfragmentasi secara struktural ke dalam tiga rezim pasar yang berbeda. Metode clustering berbasis MDDTW berhasil memetakan gradasi efisiensi pasar, mulai dari Gerombol 1 yang merepresentasikan wilayah sentra produksi dengan pasar yang sangat terintegrasi dan harga terendah, hingga Gerombol 3 yang mencerminkan wilayah defisit logistik dengan inefisiensi pasar yang parah dan volatilitas harga ekstrem. Di antara keduanya, Gerombol 2 hadir sebagai wilayah transisi yang perilakunya mengikuti tren sentra produksi namun dengan margin harga distribusi.
Temuan ini menegaskan bahwa disparitas harga antarwilayah bukan sekadar masalah perbedaan nominal rupiah, melainkan perbedaan mendasar dalam perilaku dan integrasi pasar. Tingginya heterogenitas pada Gerombol 3 menunjukkan bahwa risiko ketidakpastian harga di wilayah timur bersifat lokal dan unik (akibat kendala logistik spesifik), berbeda dengan Gerombol 1 dan 2 yang resikonya bersifat sistemik (mengikuti siklus panen nasional). Oleh karena itu, analisis ini menyimpulkan bahwa intervensi kebijakan tunggal (one-size-fits-all) tidak akan efektif; diperlukan pendekatan manajemen stok untuk wilayah stabil (Gerombol 1 & 2) dan perbaikan infrastruktur distribusi fisik untuk wilayah fluktuatif (Gerombol 3).
Kereh, P. F. F. 2025. Time Series Clustering dengan Mahalanobis Distance-Based Dynamic Time Warping pada Peramalan Tingkat Gerombol Harga Bawang Merah Provinsi di Indonesia. Skripsi. Program Studi Sarjana Statistika dan Sains Data, Sekolah Sains Data, Matematika dan Informatika, Institut Pertanian Bogor, Bogor.