Nama: Gracia Effata Tambunan
NIM: 24050123140152
Mata Kuliah: Komputasi Statistika Lanjut A
Inflasi merupakan kenaikan harga-harga secara umum dan berlangsung secara terus-menerus. Perubahan harga pada komoditas pangan sering menjadi penyumbang utama dalam pembentukan inflasi, khususnya pada komponen volatile food. Komoditas seperti cabai dan bawang memiliki karakteristik harga yang relatif mudah berfluktuasi karena dipengaruhi oleh faktor produksi, distribusi, serta kondisi musim.
Provinsi Jawa Tengah merupakan salah satu provinsi dengan jumlah penduduk yang besar di Indonesia, sehingga kebutuhan terhadap bahan pangan juga tinggi. Di sisi lain, karakteristik ekonomi, distribusi, dan aksesibilitas antar kabupaten/kota berbeda-beda sehingga dapat menyebabkan variasi harga pangan antar wilayah.
Untuk mengidentifikasi pola kemiripan harga tersebut, analisis ini menggunakan K-Means Clustering untuk mengelompokkan 12 kabupaten/kota berdasarkan karakteristik harga beberapa komoditas pangan strategis. Hasil pengelompokan diharapkan dapat menjadi informasi pendukung dalam pemantauan dan pengendalian inflasi pangan yang lebih terarah.
Data yang digunakan dalam analisis adalah data sekunder yaitu data harga pangan (Rp/kg) pada 12 kabupaten/kota di Provinsi Jawa Tengah pada tanggal 2 Januari 2026. Data tersebut bersumber dari Pusat Informasi Harga Pangan Strategis (PIHPS) Nasional yang dikelola oleh Bank Indonesia dan dapat diakses melalui situs resmi https://www.bi.go.id/hargapangan. Data yang digunakan mencakup berbagai komoditas pangan strategis yang berperan penting dalam pembentukan inflasi daerah.
harga_pangan <- data.frame(
Daerah = c(
"Kab. Banyumas", "Kab. Boyolali", "Kab. Cilacap", "Kab. Karanganyar",
"Kab. Klaten", "Kab. Kudus", "Kota Semarang", "Kab. Sragen",
"Kab. Sukoharjo", "Kota Surakarta", "Kota Tegal", "Kab. Wonogiri"
),
Beras = c(14750, 14850, 15950, 15250, 14250, 14200, 16100, 14100, 16200, 15850, 14250, 14550),
Daging_Ayam = c(40250, 36250, 39000, 37750, 35500, 40000, 37000, 36750, 37500, 36750, 40750, 36500),
Bawang_Merah = c(53750, 40000, 35250, 35000, 42250, 47500, 36250, 36750, 37250, 38250, 41250, 38750),
Cabai_Merah = c(53750, 38150, 34400, 32750, 39000, 56250, 34650, 29400, 36750, 26900, 42500, 37500),
Cabai_Rawit = c(63250, 34150, 44400, 44400, 34400, 66250, 47000, 40400, 38750, 40000, 56900, 38150),
Gula_Pasir = c(17450, 18200, 18100, 17650, 18300, 17350, 17950, 17950, 17800, 18350, 17650, 17350)
)
row.names(harga_pangan) <- harga_pangan$Daerah
data_num <- harga_pangan[, -1]
kable(
harga_pangan %>%
rename_with(~ gsub("_", " ", .x)),
caption = "**Tabel 1.** Data Harga Pangan (Rp/kg) pada 12 Kabupaten/Kota",
row.names = FALSE
)| Daerah | Beras | Daging Ayam | Bawang Merah | Cabai Merah | Cabai Rawit | Gula Pasir |
|---|---|---|---|---|---|---|
| Kab. Banyumas | 14750 | 40250 | 53750 | 53750 | 63250 | 17450 |
| Kab. Boyolali | 14850 | 36250 | 40000 | 38150 | 34150 | 18200 |
| Kab. Cilacap | 15950 | 39000 | 35250 | 34400 | 44400 | 18100 |
| Kab. Karanganyar | 15250 | 37750 | 35000 | 32750 | 44400 | 17650 |
| Kab. Klaten | 14250 | 35500 | 42250 | 39000 | 34400 | 18300 |
| Kab. Kudus | 14200 | 40000 | 47500 | 56250 | 66250 | 17350 |
| Kota Semarang | 16100 | 37000 | 36250 | 34650 | 47000 | 17950 |
| Kab. Sragen | 14100 | 36750 | 36750 | 29400 | 40400 | 17950 |
| Kab. Sukoharjo | 16200 | 37500 | 37250 | 36750 | 38750 | 17800 |
| Kota Surakarta | 15850 | 36750 | 38250 | 26900 | 40000 | 18350 |
| Kota Tegal | 14250 | 40750 | 41250 | 42500 | 56900 | 17650 |
| Kab. Wonogiri | 14550 | 36500 | 38750 | 37500 | 38150 | 17350 |
Variabel yang digunakan dalam penelitian ini adalah:
Data yang diperoleh dianalisis menggunakan pendekatan Non-Hierarchical Clustering menggunakan metode K-Means. Metode ini bertujuan untuk mengelompokkan objek-objek ke dalam k kelompok berdasarkan kesamaan karakteristiknya. Pengolahan data dilakukan dengan bantuan program R.
Langkah-langkah analisis menggunakan metode K-Means Clustering adalah sebagai berikut:
Pada bagian ini ditampilkan hasil analisis beserta interpretasinya, sesuai langkah analisis pada bagian metodologi.
Pengujian kecukupan data dilakukan untuk mengukur apakah sampel yang digunakan sudah mewakili populasi yang ada. Uji kecukupan data dapat dilakukan dengan Uji Kaiser Mayer Olkin (KMO). Uji KMO ini mengukur kecukupan sampling secara menyeluruh dan mengukur kecukupan sampling untuk setiap indikator. Uji Kaiser-Mayer-Olkin (KMO) memiliki nilai 0 sampai dengan 1. Data dinyatakan layak apabila nilai KMO ≥ 0.5. Berdasarkan hasil pengujian, diperoleh nilai KMO untuk masing-masing variabel sebagai berikut:
kmo_result <- psych::KMO(cor(data_num))
kmo_table <- data.frame(
Variabel = c("Overall", gsub("_", " ", colnames(data_num))),
Nilai_KMO = round(c(kmo_result$MSA, kmo_result$MSAi), 2)
)
knitr::kable(
kmo_table,
col.names = c("Variabel", "Nilai KMO Factor Adequacy"),
caption = "**Tabel 2.** Uji Kaiser-Meyer-Olkin (KMO) *Measure of Sampling Adequacy*",
row.names = FALSE
)| Variabel | Nilai KMO Factor Adequacy |
|---|---|
| Overall | 0.74 |
| Beras | 0.83 |
| Daging Ayam | 0.71 |
| Bawang Merah | 0.67 |
| Cabai Merah | 0.76 |
| Cabai Rawit | 0.74 |
| Gula Pasir | 0.77 |
Karena seluruh nilai KMO berada di atas 0.5, maka dapat disimpulkan bahwa sampel yang digunakan sudah cukup layak untuk dianalisis lebih lanjut.
Uji multikolinearitas dilakukan untuk memastikan bahwa antar variabel tidak memiliki hubungan yang terlalu kuat. Pengujian ini menggunakan nilai Variance Inflation Factor (VIF). Nilai VIF diperoleh dengan meregresikan setiap variabel secara bergantian sebagai variabel dependen (Y), sedangkan variabel lainnya dijadikan variabel independen (X). Suatu variabel dinyatakan tidak mengalami multikolinearitas apabila memiliki nilai VIF < 10. Berdasarkan hasil pengujian menggunakan R, diperoleh nilai VIF sebagai berikut:
vif_results <- data.frame(
Variabel = character(),
R_Squared = numeric(),
VIF = numeric()
)
for (i in seq_len(ncol(data_num))) {
target <- colnames(data_num)[i]
predictors <- colnames(data_num)[-i]
form <- as.formula(paste(target, "~", paste(predictors, collapse = "+")))
model <- lm(form, data = data_num)
r2 <- summary(model)$r.squared
vif <- 1 / (1 - r2)
vif_results <- rbind(
vif_results,
data.frame(
Variabel = target,
R_Squared = round(r2, 4),
VIF = round(vif, 3)
)
)
}
vif_results$Variabel <- gsub("_", " ", vif_results$Variabel)
names(vif_results)[names(vif_results) == "R_Squared"] <- "R Squared"
kable(
vif_results,
digits = 3,
caption = "**Tabel 3.** Hasil Pemeriksaan Multikolinearitas (VIF)",
row.names = FALSE
)| Variabel | R Squared | VIF |
|---|---|---|
| Beras | 0.294 | 1.416 |
| Daging Ayam | 0.830 | 5.895 |
| Bawang Merah | 0.822 | 5.609 |
| Cabai Merah | 0.863 | 7.283 |
| Cabai Rawit | 0.889 | 8.983 |
| Gula Pasir | 0.561 | 2.279 |
Berdasarkan Tabel 3 diperoleh bahwa nilai VIF dari setiap variabel memiliki nilai yang kurang dari 10, sehingga dapat disimpulkan bahwa tidak terjadi multikolinearitas antar variabel dan uji asumsi multikolinearitas terpenuhi.
Seluruh variabel memiliki satuan yang sama (Rp/kg), namun rentang nilainya berbeda cukup signifikan. Perbedaan skala ini dapat menyebabkan variabel dengan nilai besar mendominasi perhitungan jarak dalam clustering. Oleh karena itu, data distandarisasi menggunakan metode Z-score agar setiap variabel memiliki rata-rata 0 dan simpangan baku 1, sehingga kontribusinya menjadi seimbang dalam analisis. Berikut adalah data yang telah distandardisasi:
data_scaled <- scale(data_num)
kable(
data_scaled,
digits = 3,
col.names = gsub("_", " ", colnames(data_scaled)),
caption = "**Tabel 4.** Hasil Standardisasi Data"
)| Beras | Daging Ayam | Bawang Merah | Cabai Merah | Cabai Rawit | Gula Pasir | |
|---|---|---|---|---|---|---|
| Kab. Banyumas | -0.340 | 1.389 | 2.454 | 1.735 | 1.625 | -1.098 |
| Kab. Boyolali | -0.216 | -0.910 | -0.034 | -0.040 | -1.065 | 1.005 |
| Kab. Cilacap | 1.144 | 0.671 | -0.894 | -0.466 | -0.117 | 0.724 |
| Kab. Karanganyar | 0.278 | -0.048 | -0.939 | -0.654 | -0.117 | -0.537 |
| Kab. Klaten | -0.958 | -1.341 | 0.373 | 0.057 | -1.042 | 1.285 |
| Kab. Kudus | -1.020 | 1.245 | 1.323 | 2.019 | 1.902 | -1.379 |
| Kota Semarang | 1.329 | -0.479 | -0.713 | -0.438 | 0.123 | 0.304 |
| Kab. Sragen | -1.144 | -0.623 | -0.622 | -1.035 | -0.487 | 0.304 |
| Kab. Sukoharjo | 1.453 | -0.192 | -0.532 | -0.199 | -0.640 | -0.117 |
| Kota Surakarta | 1.020 | -0.623 | -0.351 | -1.320 | -0.524 | 1.425 |
| Kota Tegal | -0.958 | 1.677 | 0.192 | 0.455 | 1.038 | -0.537 |
| Kab. Wonogiri | -0.587 | -0.766 | -0.260 | -0.114 | -0.695 | -1.379 |
Jumlah cluster ditentukan menggunakan metode Silhouette. Nilai average silhouette width yang lebih besar menunjukkan pemisahan antar cluster yang lebih baik dan kekompakan dalam cluster yang lebih tinggi.
set.seed(123)
fviz_nbclust(data_scaled, kmeans, method = "silhouette") +
labs(
x = "Jumlah cluster (k)",
y = "Average silhouette width"
)Gambar 1. Penentuan Jumlah Cluster Optimal dengan Metode Silhouette
Untuk memperjelas, dihitung rata-rata silhouette pada rentang kandidat k.
k_range <- 2:10
sil_width <- numeric(length(k_range))
for (i in seq_along(k_range)) {
k <- k_range[i]
km_tmp <- kmeans(data_scaled, centers = k, nstart = 50)
ss <- silhouette(km_tmp$cluster, dist(data_scaled))
sil_width[i] <- mean(ss[, 3])
}
sil_table <- data.frame(
Jumlah_Cluster = k_range,
Average_Silhouette = round(sil_width, 3)
)
kable(
sil_table,
col.names = c("Jumlah *Cluster*", "Rata-Rata *Silhouette*"),
caption = "**Tabel 5.** Rata-rata *Silhouette* untuk Berbagai Nilai k",
row.names = FALSE,
align = c("c","c")
)| Jumlah Cluster | Rata-Rata Silhouette |
|---|---|
| 2 | 0.501 |
| 3 | 0.359 |
| 4 | 0.345 |
| 5 | 0.307 |
| 6 | 0.252 |
| 7 | 0.228 |
| 8 | 0.205 |
| 9 | 0.198 |
| 10 | 0.124 |
Berdasarkan metode silhouette, jumlah cluster optimal terletak pada jumlah k = 2 dengan nilai rata-rata silhouette tertinggi yaitu 0.501 yang dapat digolongkan ke dalam susunan cluster baik. Sehingga jumlah cluster optimal yang dipilih adalah 2 cluster.
set.seed(123)
km_res <- kmeans(data_scaled, centers = 2, nstart = 50)
# Tabel jumlah anggota cluster
cluster_size <- data.frame(
Cluster = 1:length(km_res$size),
Jumlah_Anggota = km_res$size
)
kable(
cluster_size,
col.names = c("*Cluster*", "Jumlah Anggota"),
caption = "**Tabel 6**. Jumlah Anggota *Cluster*",
row.names = FALSE,
align = c("c","c")
)| Cluster | Jumlah Anggota |
|---|---|
| 1 | 9 |
| 2 | 3 |
Berdasarkan output tersebut, dari 12 kabupaten/kota di Provinsi Jawa tengah dibentuk 2 cluster dengan jumlah anggota terbanyak terdapat pada cluster 1 yaitu 9 kabupaten/kota. Kemudian cluster 2 memiliki anggota sebanyak 3 kabupaten/kota. Berikut adalah tabel yang memuat informasi mengenai keanggotaan dari masing-masing cluster.
hasil_cluster <- harga_pangan %>%
mutate(Cluster = factor(km_res$cluster))
colnames(hasil_cluster) <- gsub("_", " ", colnames(hasil_cluster))
kable(
hasil_cluster,
caption = "**Tabel 7.** Keanggotaan *Cluster* untuk 12 Kabupaten/Kota",
row.names = FALSE
)| Daerah | Beras | Daging Ayam | Bawang Merah | Cabai Merah | Cabai Rawit | Gula Pasir | Cluster |
|---|---|---|---|---|---|---|---|
| Kab. Banyumas | 14750 | 40250 | 53750 | 53750 | 63250 | 17450 | 2 |
| Kab. Boyolali | 14850 | 36250 | 40000 | 38150 | 34150 | 18200 | 1 |
| Kab. Cilacap | 15950 | 39000 | 35250 | 34400 | 44400 | 18100 | 1 |
| Kab. Karanganyar | 15250 | 37750 | 35000 | 32750 | 44400 | 17650 | 1 |
| Kab. Klaten | 14250 | 35500 | 42250 | 39000 | 34400 | 18300 | 1 |
| Kab. Kudus | 14200 | 40000 | 47500 | 56250 | 66250 | 17350 | 2 |
| Kota Semarang | 16100 | 37000 | 36250 | 34650 | 47000 | 17950 | 1 |
| Kab. Sragen | 14100 | 36750 | 36750 | 29400 | 40400 | 17950 | 1 |
| Kab. Sukoharjo | 16200 | 37500 | 37250 | 36750 | 38750 | 17800 | 1 |
| Kota Surakarta | 15850 | 36750 | 38250 | 26900 | 40000 | 18350 | 1 |
| Kota Tegal | 14250 | 40750 | 41250 | 42500 | 56900 | 17650 | 2 |
| Kab. Wonogiri | 14550 | 36500 | 38750 | 37500 | 38150 | 17350 | 1 |
Jarak anggota ke centroid digunakan untuk melihat seberapa “mewakili” suatu wilayah terhadap karakteristik rata-rata cluster-nya. Jarak yang lebih kecil berarti wilayah lebih dekat dengan centroid (lebih representatif). Selain itu, jarak antar centroid menunjukkan tingkat perbedaan karakteristik rata-rata antar cluster.
centroids <- km_res$centers
distance_to_centroid <- numeric(nrow(data_scaled))
for (i in seq_len(nrow(data_scaled))) {
cl_id <- km_res$cluster[i]
distance_to_centroid[i] <- sqrt(sum((data_scaled[i, ] - centroids[cl_id, ])^2))
}
tabel_jarak <- data.frame(
Daerah = rownames(data_scaled),
Cluster = km_res$cluster,
Jarak_ke_Centroid = round(distance_to_centroid, 4)
) %>%
arrange(Cluster, Jarak_ke_Centroid)
colnames(tabel_jarak) <- gsub("_", " ", colnames(tabel_jarak))
kable(
tabel_jarak,
caption = "**Tabel 8.** Jarak Kabupaten/Kota terhadap *Centroid Cluster*",
row.names = FALSE,
align = c("l","c","c")
)| Daerah | Cluster | Jarak ke Centroid |
|---|---|---|
| Kab. Karanganyar | 1 | 1.1754 |
| Kab. Boyolali | 1 | 1.2325 |
| Kota Semarang | 1 | 1.2733 |
| Kab. Sukoharjo | 1 | 1.3467 |
| Kab. Sragen | 1 | 1.5301 |
| Kota Surakarta | 1 | 1.5891 |
| Kab. Cilacap | 1 | 1.6172 |
| Kab. Wonogiri | 1 | 1.9814 |
| Kab. Klaten | 1 | 2.0855 |
| Kab. Kudus | 2 | 0.8730 |
| Kab. Banyumas | 2 | 1.2643 |
| Kota Tegal | 2 | 1.6498 |
jarak_matrix <- as.matrix(dist(centroids))
colnames(jarak_matrix) <- paste("Cluster", 1:2)
rownames(jarak_matrix) <- paste("Cluster", 1:2)
kable(jarak_matrix, caption = "**Tabel 9.** Jarak Antar *Centroid Cluster*")| Cluster 1 | Cluster 2 | |
|---|---|---|
| Cluster 1 | 0.00000 | 4.15399 |
| Cluster 2 | 4.15399 | 0.00000 |
Visualisasi berikut merupakan proyeksi dua dimensi untuk memudahkan interpretasi pemisahan cluster. Proses clustering tetap dilakukan menggunakan seluruh variabel harga yang dianalisis.
fviz_cluster(
km_res,
data = data_scaled,
geom = "point",
ellipse.type = "convex",
show.clust.cent = FALSE
) +
labs(title = "Cluster Plot (K-Means)")Gambar 2. Visualisasi Hasil K-Means Clustering (k = 2)
Profiling dilakukan untuk menginterpretasikan karakteristik tiap cluster berdasarkan rata-rata harga komoditas.
profil_cluster <- hasil_cluster %>%
group_by(Cluster) %>%
summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)), .groups = "drop")
kable(profil_cluster, caption = "**Tabel 10.** Rata-rata Harga per Komoditas pada Setiap Cluster (Rp/kg)")| Cluster | Beras | Daging Ayam | Bawang Merah | Cabai Merah | Cabai Rawit | Gula Pasir |
|---|---|---|---|---|---|---|
| 1 | 15233.33 | 37000.00 | 37750 | 34388.89 | 40183.33 | 17961.11 |
| 2 | 14400.00 | 40333.33 | 47500 | 50833.33 | 62133.33 | 17483.33 |
Berikut adalah visualisasi dalam bentuk histogram dan heatmap.
ggplot(profil_long, aes(x = Komoditas, y = Rata_rata, fill = Cluster)) +
geom_col(position = position_dodge(width = 0.8)) +
geom_text(
aes(label = round(Rata_rata, 0)),
position = position_dodge(width = 0.8),
vjust = -0.3,
size = 3
) +
labs(
x = "Komoditas",
y = "Rata-rata Harga (Rp/kg)"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Gambar 3. Perbandingan Rata-rata Harga per Komoditas pada Setiap Cluster
data_long <- hasil_cluster %>%
pivot_longer(
cols = -c(Daerah, Cluster),
names_to = "Komoditas",
values_to = "Harga"
)
ggplot(data_long, aes(x = Komoditas, y = Daerah, fill = Harga)) +
geom_tile(color = "white") +
facet_wrap(~Cluster, scales = "free_y") +
labs(
x = "Komoditas",
y = "Kabupaten/Kota",
fill = "Harga (Rp/kg)"
) +
scale_x_discrete(labels = function(x) gsub("_", " ", x)) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Gambar 4. Heatmap Harga Pangan per Kabupaten/Kota Berdasarkan Cluster
Berdasarkan tabel rata-rata variabel tiap cluster, grafik histogram, dan heatmap, terlihat bahwa perbedaan utama antara kedua cluster terletak pada komoditas hortikultura, khususnya cabai merah, cabai rawit, dan bawang merah. Selisih terbesar terdapat pada cabai rawit (Rp21.950), diikuti cabai merah (Rp16.444) dan bawang merah (Rp9.750).
Sebaliknya, perbedaan pada komoditas pokok seperti beras (Rp833) dan gula pasir (Rp478) relatif kecil. Hal ini menunjukkan bahwa pembentukan cluster lebih dipengaruhi oleh komoditas yang bersifat volatile dibandingkan komoditas pokok yang relatif stabil.
Pola ini juga tercermin pada heatmap, di mana intensitas warna untuk komoditas hortikultura tampak lebih kontras antar cluster dibandingkan komoditas beras dan gula pasir.
Secara umum, cluster 1 dapat dikategorikan sebagai wilayah dengan tekanan harga yang relatif lebih rendah pada komoditas volatile food. Meskipun demikian, harga beras dan gula pasir pada cluster ini sedikit lebih tinggi dibandingkan cluster 2, sehingga tetap berpotensi memberikan kontribusi terhadap pembentukan inflasi mengingat bobot kedua komoditas tersebut dalam struktur pengeluaran rumah tangga.Dengan jumlah anggota yang lebih sedikit, cluster 2 dapat dipandang sebagai kelompok wilayah dengan karakteristik harga yang berbeda signifikan dari mayoritas daerah lainnya, sehingga dapat menjadi prioritas dalam strategi stabilisasi pasokan hortikultura.
Berdasarkan analisis K-Means Clustering, 12 kabupaten/kota di Provinsi Jawa Tengah dapat dikelompokkan ke dalam dua cluster berdasarkan karakteristik harga pangan. Perbedaan utama antar cluster cenderung dipengaruhi oleh komoditas yang bersifat lebih volatile, sehingga segmentasi wilayah dapat membantu melihat potensi tekanan harga pangan yang berbeda antar kelompok wilayah.
Penelitian selanjutnya disarankan menggunakan cakupan wilayah yang lebih luas serta menambahkan variabel lain yang berkaitan dengan sisi pasokan dan distribusi (misalnya produksi, akses logistik, atau jarak distribusi). Selain itu, penggunaan metode clustering lain sebagai pembanding juga dapat dipertimbangkan untuk menguji konsistensi hasil pengelompokan.