1. Pendahuluan

1.1 Latar Belakang

Pandemi Coronavirus Disease 2019 (COVID-19) telah menjadi krisis kesehatan global yang memberikan dampak multidimensional yang signifikan terhadap tatanan sosial, ketahanan sistem medis, serta stabilitas perekonomian di berbagai negara, termasuk Indonesia. Sebagai negara kepulauan dengan karakteristik geografis yang luas dan heterogenitas demografi yang tinggi, dinamika transmisi virus SARS-CoV-2 di Indonesia menunjukkan pola penyebaran yang sangat kompleks. Jumlah penduduk yang besar dan disparitas infrastruktur kesehatan antardaerah menjadi faktor determinan utama yang memengaruhi beban risiko serta kapasitas respons penanggulangan wabah di setiap wilayah administratif.

Dalam perjalanannya, akumulasi kasus terkonfirmasi positif dan angka mortalitas menunjukkan ketimpangan yang sangat nyata antarpulau dan antarprovinsi di Indonesia. Wilayah pusat pertumbuhan ekonomi dan aglomerasi perkotaan, khususnya di Pulau Jawa, tercatat sebagai episentrum utama dengan konsentrasi jutaan kasus dan puluhan ribu kematian. Sebaliknya, sebagian besar provinsi di luar Pulau Jawa menghadapi tantangan yang berbeda, seperti keterbatasan akses fasilitas rujukan intensif dan keterjangkauan logistik penanganan medis meskipun secara kumulatif mencatatkan volume kasus yang lebih rendah. Keberadaan disparitas ekstrem dan data observasi pencilan (outlier) ini menimbulkan tantangan metodologis tersendiri dalam melakukan pengelompokan wilayah secara objektif dan akurat.

Untuk merumuskan strategi penanganan pasca-pandemi (recovery phase) serta kesiapsiagaan mitigasi krisis kesehatan di masa depan, dibutuhkan pemetaan segmentasi provinsi berbasis bukti empiris yang representatif. Pendekatan analisis klaster non-hierarki menjadi solusi analitis yang efektif untuk mengidentifikasi tipologi daerah berdasarkan variabel demografi dan beban kasus medis. Penelitian ini menerapkan algoritma K-Medoids (Partitioning Around Medoids / PAM) yang memiliki keunggulan ketahanan (robustness) terhadap gangguan data ekstrem dibandingkan metode berbasis rata-rata konvensional. Dengan menetapkan titik observasi nyata sebagai pusat klaster (medoid), hasil segmentasi ini diharapkan mampu memberikan pemahaman yang komprehensif mengenai perbedaan beban kumulatif COVID-19 antarprovinsi sehingga dapat menjadi rujukan dalam merancang alokasi sumber daya kesehatan yang lebih proporsional dan tepat sasaran.

1.2 Rumusan Masalah

Berdasarkan latar belakang yang telah diuraikan, rumusan masalah dalam penelitian ini adalah sebagai berikut:

  1. Bagaimana karakteristik provinsi di Indonesia berdasarkan jumlah penduduk, total kasus COVID-19, dan total kematian akibat COVID-19?

  2. Berapa jumlah klaster optimal dalam pengelompokan provinsi di Indonesia berdasarkan metode Silhouette Coefficient?

  3. Bagaimana hasil pengelompokan dan karakteristik setiap klaster provinsi di Indonesia menggunakan algoritma K-Medoids (Partitioning Around Medoids)?

1.3 Tujuan Penelitian

Berdasarkan rumusan masalah tersebut, tujuan penelitian ini adalah sebagai berikut:

  1. Mendeskripsikan karakteristik provinsi di Indonesia berdasarkan jumlah penduduk, total kasus COVID-19, dan total kematian akibat COVID-19.

  2. Menentukan jumlah klaster optimal dalam pengelompokan provinsi di Indonesia menggunakan metode Silhouette Coefficient.

  3. Mengelompokkan provinsi di Indonesia menggunakan algoritma K-Medoids serta mengidentifikasi karakteristik dan sebaran wilayah pada setiap klaster yang terbentuk.

2. Tinjauan Pustaka

2.1 Analisis Klaster (Clustering)

Clustering adalah teknik dalam analisis data multivariat dan machine learning (unsupervised learning) untuk mengelompokkan objek-objek ke dalam beberapa kelompok (cluster) berdasarkan kesamaan karakteristiknya. Data di dalam satu kelompok diharapkan memiliki tingkat homogenitas maksimum (maximum intra-cluster similarity), sedangkan data antarkelompok memiliki tingkat heterogenitas maksimum (minimum inter-cluster similarity). Metode klasterisasi secara umum dibagi menjadi dua pendekatan utama, yaitu hierarchical clustering dan partitional clustering.

Hierarchical clustering merupakan pendekatan klasterisasi yang membentuk struktur kelompok secara bertingkat berdasarkan ukuran kedekatan atau jarak antarobjek. Pembentukan klaster dapat dilakukan melalui metode aglomeratif (agglomerative), yaitu dengan menggabungkan objek atau kelompok secara bertahap, maupun metode divisif (divisive), yaitu dengan memisahkan kelompok besar menjadi kelompok-kelompok yang lebih kecil. Hasil pengelompokan pada pendekatan ini umumnya disajikan dalam bentuk dendrogram yang menggambarkan hubungan hierarkis antarklaster.

Sementara itu, partitional clustering merupakan pendekatan yang membagi sejumlah objek secara langsung ke dalam \(k\) klaster yang tidak saling tumpang tindih. Setiap objek hanya menjadi anggota dari satu klaster berdasarkan ukuran kemiripan atau jarak terhadap pusat klaster. Proses pengelompokan umumnya dilakukan secara iteratif dengan memindahkan objek atau memperbarui pusat klaster hingga diperoleh konfigurasi yang optimal. Beberapa metode yang termasuk dalam pendekatan ini adalah K-Means dan K-Medoids. Penelitian ini menggunakan pendekatan partitional clustering melalui algoritma K-Medoids karena pusat klasternya berupa objek aktual dan relatif lebih tahan terhadap keberadaan pencilan.

2.2 Pencilan (Outlier)

Outlier atau pencilan merupakan observasi yang nilainya menyimpang secara ekstrem dari pola umum sebaran data lainnya. Adanya pencilan berpotensi mendistorsi perhitungan jarak serta membiaskan struktur penaksiran klaster. Deteksi visual terhadap pencilan umumnya dilakukan melalui diagram kotak-garis (boxplot) berbasis metode Interquartile Range (IQR), di mana titik data yang berada di luar batas whisker (\(Q_1 - 1{,}5 \times \text{IQR}\) atau \(Q_3 + 1{,}5 \times \text{IQR}\)) diklasifikasikan sebagai pencilan.

2.3 Asumsi Non-Multikolinearitas

Multikolinearitas adalah kondisi korelasi linear yang sangat tinggi antara dua atau lebih variabel prediktor. Dalam analisis klaster, korelasi ganda yang berlebih dapat menyebabkan pembobotan jarak yang tumpang tindih (redundant). Deteksi multikolinearitas diukur menggunakan nilai Variance Inflation Factor (VIF):

\[ \text{VIF}_j = \frac{1}{1 - R_j^2} \]

dengan \(R_j^2\) adalah koefisien determinasi hasil regresi variabel ke-\(j\) terhadap variabel lainnya. Nilai \(\text{VIF} < 10\) menunjukkan bahwa model bebas dari masalah multikolinearitas.

2.4 Normalisasi Min-Max

Normalisasi data merupakan tahap prapemrosesan data (data preprocessing) yang bertujuan menyelaraskan rentang nilai pada setiap variabel agar variabel dengan skala satuan besar tidak mendominasi perhitungan jarak. Metode Min-Max Scaling mengonversi data secara linear ke dalam rentang interval \([0, 1]\) menggunakan formula:

\[ x_{ij}^{*} = \frac{x_{ij} - \min_j(x)}{\max_j(x) - \min_j(x)} \]

dengan \(x_{ij}^{*}\) adalah nilai hasil normalisasi objek ke-\(i\) pada variabel ke-\(j\), \(x_{ij}\) adalah nilai asli, sedangkan \(\min_j(x)\) dan \(\max_j(x)\) berturut-turut adalah nilai minimum dan maksimum pada variabel ke-\(j\).

2.5 Algoritma K-Medoids (Partitioning Around Medoids / PAM)

K-Medoids merupakan algoritma partitional clustering yang mempartisi \(n\) objek ke dalam \(k\) kelompok berdasarkan tingkat kemiripan jarak. Berbeda dengan K-Means yang menghitung pusat klaster menggunakan rata-rata (centroid), K-Medoids memilih titik observasi riil di dalam data sebagai pusat (medoid). Hal ini membuat K-Medoids sangat tahan (robust) terhadap gangguan nilai pencilan (outlier).

Jarak antarobjek dihitung menggunakan Manhattan Distance. Jarak ini menghitung jumlah selisih absolut pada setiap variabel dan dirumuskan sebagai berikut:

\[ d(p,q) = \sum_{j=1}^{m} |p_j-q_j| \]

dengan \(d(p,q)\) adalah jarak Manhattan antara objek \(p\) dan \(q\), \(p_j\) dan \(q_j\) adalah nilai kedua objek pada variabel ke-\(j\), sedangkan \(m\) adalah jumlah variabel yang digunakan.

Langkah-langkah algoritma K-Medoids (PAM) meliputi:

  1. Menentukan jumlah klaster \(k\).

  2. Memilih \(k\) objek awal secara representatif sebagai medoid awal.

  3. Mengalokasikan setiap objek non-medoid ke medoid terdekat berdasarkan jarak Manhattan.

  4. Memilih kandidat objek non-medoid secara iteratif dan menghitung total selisih biaya penukaran simpangan (\(S\)).

  5. Melakukan pertukaran titik jika penukaran menghasilkan nilai simpangan negatif (\(S < 0\)).

  6. Mengulangi proses hingga konfigurasi medoid tidak mengalami perubahan lagi.

2.6 Silhouette Coefficient Method

Metode Silhouette Coefficient digunakan untuk mengukur validitas dan kualitas struktur pemisahan klaster. Koefisien Silhouette objek ke-\(i\) dirumuskan sebagai:

\[ s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}} \]

dengan: * \(a(i) = \frac{1}{|A| - 1} \sum_{j \in A, j \neq i} d(i, j)\) : Rata-rata jarak objek ke-\(i\) dengan seluruh objek lain di dalam klaster yang sama (\(A\)). * \(b(i) = \min_{C \neq A} \frac{1}{|C|} \sum_{j \in C} d(i, j)\) : Rata-rata jarak minimum objek ke-\(i\) dengan objek pada klaster lain terdekat (\(C\)).

Nilai rata-rata Silhouette Coefficient (\(\overline{SC}\)) berada pada rentang \([-1, 1]\) dengan kriteria interpretasi sebagai berikut (Rousseeuw, 1987):

Rentang Nilai Silhouette Keterangan Struktur Klaster
\(0{,}70 < \overline{SC} \le 1{,}00\) Struktur Kuat (Strong Structure)
\(0{,}50 < \overline{SC} \le 0{,}70\) Struktur Baik (Reasonable Structure)
\(0{,}25 < \overline{SC} \le 0{,}50\) Struktur Lemah (Weak Structure)
\(\overline{SC} \le 0{,}25\) Tidak Terstruktur (No Substantial Structure)

3. Deskripsi Data

3.1 Sumber Data

Data yang digunakan dalam penelitian ini merupakan data sekunder yang diunduh dari repositori publik Kaggle dengan judul COVID-19 Indonesia Time Series All Data melalui tautan berikut: https://www.kaggle.com/datasets/hendratno/covid19-indonesia.

3.2 Kondisi Data Awal (Raw Data)

Dataset primer yang diperoleh merupakan data runtun waktu harian (daily time-series data) yang mencakup periode 1 Maret 2020 hingga 16 September 2022 dengan volume sebanyak 31.822 baris observasi dan 38 variabel. Dataset ini memiliki struktur yang belum terstandardisasi untuk analisis (cross-section), sehingga memerlukan tahapan lanjutan agar siap digunakan untuk analisis klaster.

Adapun tampilan struktur asli dari (raw data) disajikan pada Tabel 1.

# 1. Menentukan dan memeriksa lokasi file
data_path <- "covid_19_indonesia_time_series_all.csv"

if (!file.exists(data_path)) {
  stop(
    "File covid_19_indonesia_time_series_all.csv tidak ditemukan. ",
    "Letakkan file CSV pada folder yang sama dengan file Rmd."
  )
}

# 2. Membaca dataset mentah langsung dari file CSV
raw_data <- read.csv(
  data_path,
  stringsAsFactors = FALSE,
  check.names = TRUE
)

# 3. Memastikan seluruh kolom yang diperlukan tersedia
kolom_wajib <- c(
  "Date", "Location", "Location.Level", "Province",
  "New.Cases", "Total.Cases", "Total.Deaths", "Population",
  "Longitude", "Latitude"
)

kolom_tidak_tersedia <- setdiff(kolom_wajib, names(raw_data))
if (length(kolom_tidak_tersedia) > 0) {
  stop(
    "Kolom berikut tidak ditemukan dalam dataset: ",
    paste(kolom_tidak_tersedia, collapse = ", ")
  )
}

# 4. Menampilkan beberapa baris pertama secara apa adanya dari CSV asli
cuplikan_mentah <- head(raw_data[, c("Date", "Location", "Location.Level", "Province", "New.Cases", "Total.Cases", "Total.Deaths", "Population")], 5)

kable(
  cuplikan_mentah,
  row.names = FALSE,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 1: Cuplikan Struktur Dataset Mentah Awal (Raw Data) yang Belum Diproses"
)
Tabel 1: Cuplikan Struktur Dataset Mentah Awal (Raw Data) yang Belum Diproses
Date Location Location.Level Province New.Cases Total.Cases Total.Deaths Population
3/1/2020 DKI Jakarta Province DKI Jakarta 2 39 20 10.846.145
3/2/2020 DKI Jakarta Province DKI Jakarta 2 41 20 10.846.145
3/2/2020 Indonesia Country 2 2 0 265.185.520
3/2/2020 Riau Province Riau 1 1 0 6.074.100
3/3/2020 DKI Jakarta Province DKI Jakarta 2 43 20 10.846.145

3.3 Tahapan Analisis Data

Analisis data dalam penelitian ini dilakukan menggunakan perangkat lunak R. Adapun tahapan analisis data yang dilakukan adalah sebagai berikut:

  1. Mengimpor dataset dan memeriksa struktur data, periode pengamatan, ketersediaan variabel, serta jumlah unit wilayah yang tercakup.

  2. Membersihkan dan mentransformasikan data dengan mengubah format tanggal, mengeluarkan agregat nasional Indonesia, serta mengambil observasi terakhir yang tersedia untuk setiap provinsi.

  3. Menyeleksi variabel yang digunakan dalam analisis, yaitu Population, Total_Cases, dan Total_Deaths, kemudian memastikan tidak terdapat duplikasi maupun nilai yang hilang.

  4. Melakukan eksplorasi data melalui statistik deskriptif dan visualisasi 10 provinsi dengan nilai tertinggi pada masing-masing variabel.

  5. Mengidentifikasi keberadaan pencilan (outlier) pada setiap variabel menggunakan diagram kotak-garis (boxplot).

  6. Memeriksa non-multikolinearitas antarvariabel menggunakan matriks korelasi Pearson dan nilai Variance Inflation Factor (VIF).

  7. Melakukan normalisasi Min-Max untuk mentransformasikan setiap variabel ke dalam rentang \([0,1]\) sehingga perbedaan skala tidak mendominasi perhitungan jarak.

  8. Menentukan jumlah klaster optimal menggunakan metode Average Silhouette Width pada beberapa kandidat nilai \(k\) dengan jarak Manhattan.

  9. Memberikan ilustrasi perhitungan algoritma K-Medoids, mulai dari pemilihan medoid awal, pengalokasian objek, perhitungan biaya setiap klaster, hingga pertukaran medoid pada iterasi pertama.

  10. Membentuk klaster provinsi menggunakan algoritma K-Medoids (Partitioning Around Medoids / PAM) berdasarkan jumlah klaster optimal dan jarak Manhattan.

  11. Menginterpretasikan hasil klasterisasi melalui identifikasi medoid, jarak antarmedoid, rata-rata karakteristik setiap klaster, profilisasi klaster, serta visualisasi keanggotaan klaster dalam bentuk peta.

3.4 Tahapan Pembersihan dan Transformasi Data

Dataset mencakup 35 lokasi, yang terdiri atas 34 provinsi dan satu agregat tingkat negara, yaitu Indonesia. Karena unit analisis penelitian adalah provinsi, baris Indonesia tidak dimasukkan ke dalam proses klasterisasi. Data setiap provinsi kemudian diambil dari tanggal terakhir yang tersedia untuk provinsi tersebut. Pendekatan ini digunakan agar provinsi yang tidak memiliki observasi pada tanggal maksimum dataset tetap dapat dipertahankan menggunakan observasi terbarunya.

Selanjutnya, dilakukan seleksi fitur dengan menetapkan tiga indikator utama yang merepresentasikan skala demografi dan tingkat keparahan epidemiologis di setiap daerah, meliputi Population (\(X_1\)), Total_Cases (\(X_2\)), dan Total_Deaths (\(X_3\)). Struktur data bersih hasil transformasi tersebut ditampilkan pada Tabel 2.

# 1. Mengubah kolom tanggal menjadi format Date
raw_data <- raw_data %>%
  mutate(Date_Parsed = as.Date(Date, format = "%m/%d/%Y"))

if (any(is.na(raw_data$Date_Parsed))) {
  stop("Terdapat nilai tanggal yang tidak berhasil dikonversi.")
}

# 2. Memeriksa jumlah seluruh lokasi dan jumlah provinsi dalam dataset
jumlah_lokasi_total <- n_distinct(raw_data$Location)
jumlah_provinsi_dataset <- raw_data %>%
  filter(Location.Level == "Province") %>%
  summarise(Jumlah = n_distinct(Province)) %>%
  pull(Jumlah)

jumlah_agregat_nasional <- raw_data %>%
  filter(Location.Level == "Country") %>%
  summarise(Jumlah = n_distinct(Location)) %>%
  pull(Jumlah)

validasi_unit <- data.frame(
  Kategori = c("Seluruh lokasi dalam dataset", "Provinsi", "Agregat nasional (Indonesia)"),
  Jumlah = c(jumlah_lokasi_total, jumlah_provinsi_dataset, jumlah_agregat_nasional)
)

kable(
  validasi_unit,
  row.names = FALSE,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Validasi Jumlah Lokasi dan Provinsi dalam Dataset"
)
Validasi Jumlah Lokasi dan Provinsi dalam Dataset
Kategori Jumlah
Seluruh lokasi dalam dataset 35
Provinsi 34
Agregat nasional (Indonesia) 1
# 3. Mengambil observasi paling akhir yang tersedia untuk SETIAP provinsi
#    Dengan cara ini, provinsi yang tidak tersedia pada 15 September 2022
#    tetap diambil dari tanggal terakhir sebelumnya.
covid_prov <- raw_data %>%
  filter(
    Location.Level == "Province",
    !is.na(Province),
    Province != "",
    !is.na(Date_Parsed)
  ) %>%
  arrange(Province, desc(Date_Parsed)) %>%
  distinct(Province, .keep_all = TRUE) %>%
  arrange(Province)

# 4. Mengidentifikasi provinsi yang tanggal terakhirnya lebih awal
#    daripada tanggal maksimum seluruh data provinsi
tanggal_maksimum <- max(covid_prov$Date_Parsed)
prov_tanggal_sebelumnya <- covid_prov %>%
  filter(Date_Parsed < tanggal_maksimum) %>%
  select(Province, Tanggal_Terakhir = Date_Parsed)

if (nrow(prov_tanggal_sebelumnya) > 0) {
  tabel_tanggal_sebelumnya <- prov_tanggal_sebelumnya %>%
    mutate(Tanggal_Terakhir = format(Tanggal_Terakhir, "%d-%m-%Y"))

  print(kable(
    tabel_tanggal_sebelumnya,
    row.names = FALSE,
    caption = "Provinsi yang Menggunakan Observasi Terakhir Sebelum Tanggal Maksimum"
  ))
}
## 
## 
## Table: Provinsi yang Menggunakan Observasi Terakhir Sebelum Tanggal Maksimum
## 
## |Province       |Tanggal_Terakhir |
## |:--------------|:----------------|
## |Gorontalo      |14-09-2022       |
## |Sulawesi Barat |14-09-2022       |
# 5. Memastikan seluruh 34 provinsi masuk dalam data akhir.
#    Angka 35 pada dataset berasal dari 34 provinsi + 1 agregat Indonesia.
if (jumlah_lokasi_total != 35 || jumlah_provinsi_dataset != 34) {
  stop(
    "Validasi jumlah unit tidak sesuai: ditemukan ", jumlah_lokasi_total,
    " lokasi dan ", jumlah_provinsi_dataset, " provinsi."
  )
}

if (nrow(covid_prov) != jumlah_provinsi_dataset) {
  stop("Tidak semua provinsi berhasil diambil pada tahap pemilihan tanggal terakhir.")
}

# 6. Seleksi 3 variabel utama
data_cluster <- covid_prov %>%
  transmute(
    Province,
    Population = as.numeric(Population),
    Total_Cases = as.numeric(Total.Cases),
    Total_Deaths = as.numeric(Total.Deaths)
  )

# 7. Memastikan data analisis tidak mengandung duplikasi atau nilai kosong
if (anyDuplicated(data_cluster$Province) > 0) {
  stop("Masih terdapat nama provinsi yang duplikat dalam data akhir.")
}

if (anyNA(data_cluster)) {
  stop("Data akhir masih mengandung missing value.")
}

# 8. Membentuk matriks numerik untuk analisis klaster
#    Nama provinsi disimpan sebagai row names hanya untuk identitas komputasi.
data_analysis <- as.data.frame(data_cluster[, c("Population", "Total_Cases", "Total_Deaths")])
rownames(data_analysis) <- data_cluster$Province

# 9. Menampilkan tabel data bersih dengan Province sebagai kolom eksplisit
tabel_data_bersih <- head(data_cluster, 10)

kable(
  tabel_data_bersih,
  row.names = FALSE,
  digits = 2,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 2: Data Bersih Hasil Transformasi pada 10 Provinsi Pertama"
)
Tabel 2: Data Bersih Hasil Transformasi pada 10 Provinsi Pertama
Province Population Total_Cases Total_Deaths
Aceh 5.247.257 44.038 2.223
Bali 4.216.171 166.831 4.731
Banten 10.722.374 333.875 2.950
Bengkulu 1.999.539 29.173 522
DKI Jakarta 10.846.145 1.412.511 15.513
Daerah Istimewa Yogyakarta 3.631.015 224.307 5.928
Gorontalo 1.180.651 13.951 487
Jambi 3.493.357 38.643 889
Jawa Barat 45.161.325 1.173.731 15.937
Jawa Tengah 36.364.072 636.409 33.489

Hasil validasi menunjukkan bahwa dataset memiliki 35 lokasi, yaitu 34 provinsi dan satu agregat nasional Indonesia. Sebanyak 32 provinsi menggunakan data tanggal 15 September 2022, sedangkan 2 provinsi, yaitu Gorontalo dan Sulawesi Barat, menggunakan observasi terakhir pada 14 September 2022. Dengan mekanisme tersebut, seluruh 34 provinsi tetap tercakup dalam analisis.

4. Tahap Eksplorasi Data

4.1 Statistik Deskriptif

# Menghitung ringkasan statistik deskriptif
stat_desc <- data.frame(
  Variabel = colnames(data_analysis),
  N        = sapply(data_analysis, length),
  Mean     = sapply(data_analysis, mean),
  Median   = sapply(data_analysis, median),
  SD       = sapply(data_analysis, sd),
  Min      = sapply(data_analysis, min),
  Max      = sapply(data_analysis, max)
)

kable(stat_desc, 
      row.names = FALSE,
      digits = 2, 
      format.args = list(big.mark = ".", decimal.mark = ","),
      caption = "Tabel 3: Ringkasan Statistik Deskriptif Variabel Analisis")
Tabel 3: Ringkasan Statistik Deskriptif Variabel Analisis
Variabel N Mean Median SD Min Max
Population 34 7.799.574,12 4.119.610,0 10.936.214,86 648.407 45.161.325
Total_Cases 34 188.157,56 68.513,5 316.952,79 13.951 1.412.511
Total_Deaths 34 4.643,91 1.810,5 7.966,92 295 33.489

Berdasarkan Tabel 3, sebaran nilai populasi berkisar antara 648.407 hingga 45.161.325 jiwa, total kasus antara 13.951 hingga 1.412.511 kasus, dan angka kematian antara 295 hingga 33.489 jiwa.

4.2 Visualisasi Perbandingan Wilayah (Top 10 Tertinggi)

Untuk melengkapi pemahaman statistik deskriptif secara visual, Gambar 1 menyajikan perbandingan 10 provinsi dengan populasi, akumulasi kasus positif, dan akumulasi kematian tertinggi di Indonesia. Ketiga grafik menggunakan satuan yang disederhanakan agar perbedaan antardaerah lebih mudah diamati.

top10_population <- data_cluster %>%
  arrange(desc(Population)) %>%
  slice(1:10)

top10_cases <- data_cluster %>%
  arrange(desc(Total_Cases)) %>%
  slice(1:10)

top10_deaths <- data_cluster %>%
  arrange(desc(Total_Deaths)) %>%
  slice(1:10)

par(mfrow = c(1, 3), mar = c(8, 4, 3, 1))

# Barplot 1: 10 Provinsi dengan Populasi Tertinggi
barplot(top10_population$Population / 1000000,
        names.arg = top10_population$Province,
        las = 2,
        col = "#2ca25f",
        main = "Top 10 Populasi (Juta)",
        ylab = "Jumlah Penduduk (Juta)",
        cex.names = 0.7)

# Barplot 2: 10 Provinsi dengan Total Kasus Tertinggi
barplot(top10_cases$Total_Cases / 1000, 
        names.arg = top10_cases$Province, 
        las = 2, 
        col = "#3498db",
        main = "Top 10 Total Kasus (Ribu)",
        ylab = "Jumlah Kasus (Ribu)",
        cex.names = 0.7)

# Barplot 3: 10 Provinsi dengan Total Kematian Tertinggi
barplot(top10_deaths$Total_Deaths / 1000, 
        names.arg = top10_deaths$Province, 
        las = 2, 
        col = "#e74c3c",
        main = "Top 10 Total Kematian (Ribu)",
        ylab = "Jumlah Kematian (Ribu)",
        cex.names = 0.7)

par(mfrow = c(1, 1))
Gambar 1: Perbandingan 10 Provinsi dengan Populasi, Akumulasi Kasus, dan Kematian Tertinggi


Berdasarkan Gambar 1, provinsi dengan populasi tertinggi adalah Jawa Barat, provinsi dengan akumulasi kasus tertinggi adalah DKI Jakarta, sedangkan provinsi dengan akumulasi kematian tertinggi adalah Jawa Tengah. Visualisasi tersebut menunjukkan bahwa provinsi dengan jumlah penduduk terbesar tidak selalu menjadi provinsi dengan akumulasi kasus atau kematian tertinggi.

4.3 Pengecekan Outlier

Pengecekan keberadaan nilai pencilan (outlier) dilakukan menggunakan visualisasi Boxplot untuk masing-masing variabel:

par(mfrow = c(1, 3))
boxplot(data_analysis$Population, main = "Boxplot Population", col = "lightblue", ylab = "Jiwa")
boxplot(data_analysis$Total_Cases, main = "Boxplot Total Cases", col = "lightgreen", ylab = "Kasus")
boxplot(data_analysis$Total_Deaths, main = "Boxplot Total Deaths", col = "salmon", ylab = "Jiwa")

par(mfrow = c(1, 1))
Gambar 2: Boxplot Distribusi dan Identifikasi Outlier Variabel Analisis


Visualisasi boxplot pada Gambar 2 mengonfirmasi keberadaan titik-titik data ekstrem di atas batas kuartil atas, yang merupakan provinsi aglomerasi padat di Pulau Jawa. Hal ini mempertegas justifikasi pemilihan metode K-Medoids yang memiliki ketahanan (robustness) tinggi terhadap outlier.

5. Tahap Pre-Processing Data

5.1 Pemeriksaan Non-Multikolinearitas

Pemeriksaan non-multikolinearitas dilakukan untuk memastikan tidak adanya korelasi linear yang berlebih atau informasi yang tumpang tindih (redundant) antarvariabel, sehingga perhitungan jarak pada algoritma K-Medoids tidak memberikan bobot berulang pada karakteristik yang sama.

Deteksi multikolinearitas diuji menggunakan matriks korelasi Pearson serta perhitungan nilai Variance Inflation Factor (VIF). Nilai \(\text{VIF} < 10\) mengindikasikan bahwa antarvariabel bebas dari gangguan multikolinearitas.

# Matriks Korelasi
mat_korelasi <- cor(data_analysis)
corrplot(mat_korelasi, method = "number", type = "upper", tl.col = "black")

# Menghitung VIF untuk setiap variabel secara bergantian.
# Setiap variabel diregresikan terhadap seluruh variabel lainnya.
hitung_vif <- function(nama_variabel, data) {
  variabel_lain <- setdiff(names(data), nama_variabel)
  model_vif <- lm(
    reformulate(variabel_lain, response = nama_variabel),
    data = data
  )
  1 / (1 - summary(model_vif)$r.squared)
}

vif_df <- data.frame(
  Variabel = names(data_analysis),
  VIF = sapply(names(data_analysis), hitung_vif, data = data_analysis),
  row.names = NULL
)

kable(
  vif_df,
  row.names = FALSE,
  digits = 2,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 4: Nilai Variance Inflation Factor (VIF)"
)
Tabel 4: Nilai Variance Inflation Factor (VIF)
Variabel VIF
Population 4,44
Total_Cases 2,18
Total_Deaths 4,44

Interpretasi:
Berdasarkan Tabel 4, nilai VIF terbesar adalah 4,44. Karena seluruh nilai VIF berada di bawah ambang batas 10, tidak terdapat indikasi multikolinearitas yang berlebihan. Dengan demikian, variabel Population, Total_Cases, dan Total_Deaths tetap digunakan secara bersamaan dalam proses klasterisasi.

5.2 Normalisasi Data (Min-Max Scaling)

Agar perbedaan skala satuan antarvariabel tidak mendistorsi perhitungan jarak Manhattan, dilakukan transformasi Min-Max Scaling ke rentang interval \([0, 1]\):

\[x_{\text{scaled}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}\]

# Fungsi Min-Max Scaling
min_max_scale <- function(x) {
  return((x - min(x)) / (max(x) - min(x)))
}

scaled_data <- as.data.frame(lapply(data_analysis, min_max_scale))
rownames(scaled_data) <- rownames(data_analysis)

# Menambahkan kembali Province khusus untuk tampilan Tabel 5
tabel_scaled <- data.frame(
  Province = rownames(scaled_data),
  scaled_data,
  row.names = NULL,
  check.names = FALSE
)

# Cuplikan data setelah dinormalisasi dengan Province tetap ditampilkan
kable(
  head(tabel_scaled, 6),
  row.names = FALSE,
  digits = 4,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 5: Data Hasil Min-Max Scaling (6 Baris Pertama)"
)
Tabel 5: Data Hasil Min-Max Scaling (6 Baris Pertama)
Province Population Total_Cases Total_Deaths
Aceh 0,1033 0,0215 0,0581
Bali 0,0802 0,1093 0,1336
Banten 0,2263 0,2288 0,0800
Bengkulu 0,0304 0,0109 0,0068
DKI Jakarta 0,2291 1,0000 0,4585
Daerah Istimewa Yogyakarta 0,0670 0,1504 0,1697

6. Tahap Klasterisasi

6.1 Penentuan Jumlah Klaster Optimal (Silhouette Method)

Metode Average Silhouette Width digunakan untuk mengukur seberapa baik setiap objek berada di dalam kelompoknya.

# Menghitung Average Silhouette Width untuk k = 2 sampai k = 10
k_maksimum <- min(10, nrow(scaled_data) - 1)
kandidat_k <- 2:k_maksimum

nilai_silhouette <- sapply(kandidat_k, function(k) {
  model_k <- pam(scaled_data, k = k, metric = "manhattan")
  model_k$silinfo$avg.width
})

silhouette_df <- data.frame(
  K = kandidat_k,
  Average_Silhouette_Width = nilai_silhouette
)

k_opt <- silhouette_df$K[which.max(silhouette_df$Average_Silhouette_Width)]
silhouette_optimal <- max(silhouette_df$Average_Silhouette_Width)

kategori_silhouette <- if (
  silhouette_optimal > 0.70
) {
  "struktur kuat"
} else if (silhouette_optimal > 0.50) {
  "struktur baik"
} else if (silhouette_optimal > 0.25) {
  "struktur lemah"
} else {
  "tidak memiliki struktur yang kuat"
}

ggplot(silhouette_df, aes(x = K, y = Average_Silhouette_Width)) +
  geom_line(color = "#2c7fb8", linewidth = 0.9) +
  geom_point(color = "#2c7fb8", size = 2.8) +
  geom_point(
    data = subset(silhouette_df, K == k_opt),
    color = "#d7301f",
    size = 4
  ) +
  scale_x_continuous(breaks = kandidat_k) +
  labs(
    title = "Penentuan Jumlah Klaster Optimal",
    subtitle = "Berdasarkan Average Silhouette Width",
    x = "Jumlah Klaster (k)",
    y = "Average Silhouette Width"
  ) +
  theme_minimal()

Berdasarkan grafik evaluasi Silhouette, nilai tertinggi berada pada $k = 3 dengan koefisien rata-rata Silhouette Width sebesar 0,853. Berdasarkan kriteria interpretasi Silhouette, hasil tersebut termasuk dalam kategori struktur kuat.

6.2 Ilustrasi Perhitungan Algoritma K-Medoids

Berdasarkan hasil penentuan jumlah klaster pada Subbab 6.1, jumlah klaster optimal yang digunakan adalah \(k = 3\). Oleh karena itu, ilustrasi perhitungan berikut dimulai dari tahap pemilihan medoid awal hingga pelaksanaan pertukaran medoid pada iterasi pertama. Perhitungan menggunakan tiga variabel hasil normalisasi Min-Max, yaitu Population, Total_Cases, dan Total_Deaths.

6.2.1 Pemilihan Medoid Awal

Agar mekanisme pertukaran medoid dapat diperlihatkan secara jelas, tiga provinsi dipilih sebagai medoid awal ilustrasi, yaitu Aceh, DKI Jakarta, dan Jawa Timur. Ketiganya digunakan untuk menggambarkan proses perhitungan satu iterasi dan tidak mengubah hasil akhir pengolahan menggunakan fungsi PAM.

# Memastikan ilustrasi hanya dijalankan ketika hasil optimal adalah tiga klaster
if (k_opt != 3) {
  stop("Ilustrasi perhitungan pada Subbab 6.2 disusun untuk k = 3.")
}

medoid_awal <- c("Aceh", "DKI Jakarta", "Jawa Timur")

if (!all(medoid_awal %in% rownames(scaled_data))) {
  stop("Salah satu medoid awal ilustrasi tidak ditemukan dalam data.")
}

# Fungsi untuk menghitung jarak Manhattan seluruh provinsi ke medoid
hitung_jarak_manhattan <- function(data, nama_medoid) {
  matriks_data <- as.matrix(data)
  matriks_medoid <- matriks_data[nama_medoid, , drop = FALSE]

  matriks_jarak <- sapply(seq_len(nrow(matriks_medoid)), function(j) {
    rowSums(abs(sweep(
      matriks_data,
      MARGIN = 2,
      STATS = matriks_medoid[j, ],
      FUN = "-"
    )))
  })

  matriks_jarak <- as.matrix(matriks_jarak)
  rownames(matriks_jarak) <- rownames(data)
  colnames(matriks_jarak) <- nama_medoid
  matriks_jarak
}

# Fungsi untuk memperoleh alokasi terdekat dan total biaya konfigurasi medoid
ringkas_konfigurasi <- function(data, nama_medoid) {
  matriks_jarak <- hitung_jarak_manhattan(data, nama_medoid)
  indeks_terdekat <- max.col(-matriks_jarak, ties.method = "first")
  biaya_objek <- matriks_jarak[
    cbind(seq_len(nrow(matriks_jarak)), indeks_terdekat)
  ]

  list(
    jarak = matriks_jarak,
    indeks_terdekat = indeks_terdekat,
    medoid_terdekat = nama_medoid[indeks_terdekat],
    biaya_objek = biaya_objek,
    total_biaya = sum(biaya_objek)
  )
}

# Fungsi untuk menyusun hasil alokasi seluruh provinsi pada suatu konfigurasi
buat_tabel_alokasi <- function(konfigurasi, nama_medoid) {
  daftar_anggota <- lapply(nama_medoid, function(medoid) {
    sort(rownames(scaled_data)[
      konfigurasi$medoid_terdekat == medoid
    ])
  })

  tabel <- data.frame(
    Klaster = as.character(seq_along(nama_medoid)),
    Medoid = nama_medoid,
    Anggota = vapply(
      daftar_anggota,
      function(x) paste(x, collapse = ", "),
      character(1)
    ),
    Jumlah_Anggota = lengths(daftar_anggota),
    stringsAsFactors = FALSE,
    check.names = FALSE
  )

  names(tabel) <- c("Klaster", "Medoid", "Anggota", "Jumlah Anggota")

  rbind(
    tabel,
    data.frame(
      Klaster = "Total",
      Medoid = "-",
      Anggota = "-",
      `Jumlah Anggota` = sum(tabel$`Jumlah Anggota`),
      stringsAsFactors = FALSE,
      check.names = FALSE
    )
  )
}

# Fungsi untuk memperlihatkan asal biaya C1, C2, dan C3.
# Setiap komponen merupakan jarak satu anggota terhadap medoid klasternya.
buat_tabel_biaya_klaster <- function(konfigurasi, nama_medoid) {
  do.call(
    rbind,
    lapply(seq_along(nama_medoid), function(j) {
      indeks <- which(
        konfigurasi$medoid_terdekat == nama_medoid[j]
      )
      nama_anggota <- rownames(scaled_data)[indeks]
      urutan <- order(nama_anggota)
      indeks <- indeks[urutan]
      nama_anggota <- nama_anggota[urutan]
      kontribusi <- konfigurasi$biaya_objek[indeks]

      data.frame(
        Komponen_Biaya = paste0("C", j),
        Medoid = nama_medoid[j],
        Rincian = paste0(
          nama_anggota,
          " (",
          format_angka_id(kontribusi, 6),
          ")",
          collapse = " + "
        ),
        Total = format_angka_id(sum(kontribusi), 6),
        stringsAsFactors = FALSE,
        check.names = FALSE
      )
    })
  ) %>%
    setNames(c(
      "Komponen Biaya",
      "Medoid",
      "Kontribusi Jarak Setiap Anggota",
      "Total Biaya"
    ))
}

# Pada keluaran PDF, kolom berisi daftar anggota dibuat membungkus otomatis.
# Untuk keluaran HTML dan Word, knitr akan menggunakan tabel biasa.
tampilkan_tabel_alokasi <- function(tabel, caption) {
  if (knitr::is_latex_output()) {
    tabel_latex <- tabel
    bukan_total <- tabel_latex$Klaster != "Total"
    tabel_latex$Anggota[bukan_total] <- paste0(
      "\\parbox[t]{8cm}{",
      tabel_latex$Anggota[bukan_total],
      "}"
    )

    kable(
      tabel_latex,
      format = "latex",
      row.names = FALSE,
      escape = FALSE,
      align = c("c", "l", "l", "c"),
      caption = caption
    )
  } else {
    kable(
      tabel,
      row.names = FALSE,
      align = c("c", "l", "l", "c"),
      caption = caption
    )
  }
}

tampilkan_tabel_biaya <- function(tabel, caption) {
  if (knitr::is_latex_output()) {
    tabel_latex <- tabel
    tabel_latex$`Kontribusi Jarak Setiap Anggota` <- paste0(
      "\\parbox[t]{8cm}{",
      tabel_latex$`Kontribusi Jarak Setiap Anggota`,
      "}"
    )

    kable(
      tabel_latex,
      format = "latex",
      row.names = FALSE,
      escape = FALSE,
      align = c("c", "l", "l", "r"),
      caption = caption
    )
  } else {
    kable(
      tabel,
      row.names = FALSE,
      align = c("c", "l", "l", "r"),
      caption = caption
    )
  }
}

konfigurasi_awal <- ringkas_konfigurasi(scaled_data, medoid_awal)
biaya_awal <- konfigurasi_awal$total_biaya

# Menyimpan komponen perhitungan contoh Bali terhadap Aceh
komponen_bali_aceh <- abs(
  as.numeric(scaled_data["Bali", ]) -
    as.numeric(scaled_data["Aceh", ])
)
jarak_bali_aceh <- sum(komponen_bali_aceh)

tabel_medoid_awal <- data.frame(
  Medoid_Awal = medoid_awal,
  scaled_data[medoid_awal, , drop = FALSE],
  row.names = NULL,
  check.names = FALSE
)

kable(
  tabel_medoid_awal,
  row.names = FALSE,
  digits = 6,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 6: Nilai Normalisasi Medoid Awal pada Ilustrasi Perhitungan"
)
Tabel 6: Nilai Normalisasi Medoid Awal pada Ilustrasi Perhitungan
Medoid_Awal Population Total_Cases Total_Deaths
Aceh 0,103315 0,021513 0,058083
DKI Jakarta 0,229096 1,000000 0,458456
Jawa Timur 0,894810 0,420142 0,948033

6.2.2 Perhitungan Jarak dan Alokasi Objek

Jarak setiap provinsi terhadap masing-masing medoid awal dihitung menggunakan Manhattan Distance. Sebagai contoh, jarak antara Provinsi Bali dan medoid Aceh dihitung sebagai berikut:

\[ \begin{aligned} d(\text{Bali},\text{Aceh}) ={}&|0{,}080151-0{,}103315| \\ &+|0{,}109312-0{,}021513| \\ &+|0{,}133639-0{,}058083| \\ ={}&0{,}023164 + 0{,}087800 + 0{,}075556 \\ ={}&0{,}186519. \end{aligned} \]

Perhitungan yang sama dilakukan terhadap seluruh medoid. Sebagian hasil perhitungan jarak pada konfigurasi awal disajikan pada Tabel 7.

provinsi_contoh <- c(
  "Aceh", "Bali", "Banten", "DKI Jakarta",
  "Jawa Barat", "Jawa Tengah", "Jawa Timur", "Sulawesi Tengah"
)

tabel_jarak_awal <- data.frame(
  Provinsi = provinsi_contoh,
  konfigurasi_awal$jarak[provinsi_contoh, , drop = FALSE],
  Medoid_Terdekat = konfigurasi_awal$medoid_terdekat[
    match(provinsi_contoh, rownames(scaled_data))
  ],
  row.names = NULL,
  check.names = FALSE
)

names(tabel_jarak_awal)[2:4] <- paste("Jarak ke", medoid_awal)

kable(
  tabel_jarak_awal,
  row.names = FALSE,
  digits = 6,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 7: Contoh Jarak Manhattan dan Alokasi pada Konfigurasi Awal"
)
Tabel 7: Contoh Jarak Manhattan dan Alokasi pada Konfigurasi Awal
Provinsi Jarak ke Aceh Jarak ke DKI Jakarta Jarak ke Jawa Timur Medoid_Terdekat
Aceh 0,000000 1,504642 2,080075 Aceh
Bali 0,186519 1,364450 1,939883 Aceh
Banten 0,352142 1,152500 1,727933 Aceh
DKI Jakarta 1,504642 0,000000 1,735148 DKI Jakarta
Jawa Barat 2,117586 0,954410 0,991118 DKI Jakarta
Jawa Tengah 2,064526 1,669743 0,169340 Jawa Timur
Jawa Timur 2,080075 1,735148 0,000000 Jawa Timur
Sulawesi Tengah 0,078444 1,558688 2,134121 Aceh
jumlah_anggota_awal <- table(factor(
  konfigurasi_awal$medoid_terdekat,
  levels = medoid_awal
))

biaya_per_medoid_awal <- tapply(
  konfigurasi_awal$biaya_objek,
  factor(konfigurasi_awal$medoid_terdekat, levels = medoid_awal),
  sum
)

Setiap provinsi dialokasikan kepada medoid dengan jarak terkecil. Berdasarkan perhitungan terhadap seluruh 34 provinsi, klaster dengan medoid Aceh beranggotakan 30 provinsi, klaster dengan medoid DKI Jakarta beranggotakan 2 provinsi, dan klaster dengan medoid Jawa Timur beranggotakan 2 provinsi.

Hasil alokasi seluruh provinsi terhadap medoid awal disajikan pada Tabel 8. Anggota klaster sudah diurutkan secara alfabetis, sedangkan baris total digunakan untuk memastikan bahwa seluruh provinsi telah teralokasi tepat ke satu klaster.

tabel_alokasi_awal <- buat_tabel_alokasi(
  konfigurasi_awal,
  medoid_awal
)

tampilkan_tabel_alokasi(
  tabel_alokasi_awal,
  caption = "Tabel 8: Hasil Alokasi Provinsi terhadap Medoid Awal"
)
Tabel 8: Hasil Alokasi Provinsi terhadap Medoid Awal
Klaster Medoid Anggota Jumlah Anggota
1 Aceh Aceh, Bali, Banten, Bengkulu, Daerah Istimewa Yogyakarta, Gorontalo, Jambi, Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Maluku, Maluku Utara, Nusa Tenggara Barat, Nusa Tenggara Timur, Papua, Papua Barat, Riau, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara, Sumatera Barat, Sumatera Selatan, Sumatera Utara 30
2 DKI Jakarta DKI Jakarta, Jawa Barat 2
3 Jawa Timur Jawa Tengah, Jawa Timur 2
Total - - 34

Nilai \(C_1\), \(C_2\), dan \(C_3\) bukan merupakan jarak satu provinsi, melainkan jumlah jarak seluruh anggota pada masing-masing klaster terhadap medoidnya. Pada Tabel 9, setiap bentuk Provinsi (nilai) menunjukkan kontribusi jarak Manhattan provinsi tersebut terhadap total biaya klasternya. Jarak suatu medoid terhadap dirinya sendiri bernilai nol.

tabel_biaya_awal <- buat_tabel_biaya_klaster(
  konfigurasi_awal,
  medoid_awal
)

tampilkan_tabel_biaya(
  tabel_biaya_awal,
  caption = paste(
    "Tabel 9: Rincian Perhitungan C1, C2, dan C3",
    "pada Konfigurasi Medoid Awal"
  )
)
Tabel 9: Rincian Perhitungan C1, C2, dan C3 pada Konfigurasi Medoid Awal
Komponen Biaya Medoid Kontribusi Jarak Setiap Anggota Total Biaya
C1 Aceh Aceh (0,000000) + Bali (0,186519) + Banten (0,352142) + Bengkulu (0,134834) + Daerah Istimewa Yogyakarta (0,276822) + Gorontalo (0,165169) + Jambi (0,083448) + Kalimantan Barat (0,052232) + Kalimantan Selatan (0,069407) + Kalimantan Tengah (0,090100) + Kalimantan Timur (0,261575) + Kalimantan Utara (0,145363) + Kepulauan Bangka Belitung (0,120977) + Kepulauan Riau (0,103824) + Lampung (0,168077) + Maluku (0,152560) + Maluku Utara (0,166462) + Nusa Tenggara Barat (0,045884) + Nusa Tenggara Timur (0,060674) + Papua (0,074082) + Papua Barat (0,156113) + Riau (0,163384) + Sulawesi Barat (0,158269) + Sulawesi Selatan (0,173618) + Sulawesi Tengah (0,078444) + Sulawesi Tenggara (0,121620) + Sulawesi Utara (0,095201) + Sumatera Barat (0,053901) + Sumatera Selatan (0,128749) + Sumatera Utara (0,330477) 4,169928
C2 DKI Jakarta DKI Jakarta (0,000000) + Jawa Barat (0,954410) 0,954410
C3 Jawa Timur Jawa Tengah (0,169340) + Jawa Timur (0,000000) 0,169340

Berdasarkan rincian tersebut, biaya setiap klaster diperoleh sebagai berikut:

\[ \begin{aligned} C_1 &=\sum_{X_i\in G_1}d(X_i,\text{Aceh}) =4{,}169928, \\ C_2 &=\sum_{X_i\in G_2}d(X_i,\text{DKI Jakarta}) =0{,}954410, \\ C_3 &=\sum_{X_i\in G_3}d(X_i,\text{Jawa Timur}) =0{,}169340. \end{aligned} \]

Total biaya pada konfigurasi medoid awal adalah:

\[ \begin{aligned} C_{\text{awal}} &=C_1+C_2+C_3 \\ &=4{,}169928 + 0{,}954410 + 0{,}169340 \\ &=5{,}293678. \end{aligned} \]

6.2.3 Pemilihan Kandidat dan Alokasi terhadap Medoid Baru

Pada tahap pertukaran, setiap objek non-medoid dievaluasi sebagai kandidat pengganti masing-masing medoid awal. Nilai simpangan pertukaran dihitung menggunakan \(S=C_{\text{baru}}-C_{\text{awal}}\). Lima kandidat dengan nilai \(S\) terkecil disajikan pada Tabel 10.

non_medoid <- setdiff(rownames(scaled_data), medoid_awal)

# Menguji seluruh pasangan pertukaran medoid dan objek non-medoid
hasil_pertukaran <- do.call(
  rbind,
  lapply(seq_along(medoid_awal), function(posisi_medoid) {
    do.call(
      rbind,
      lapply(non_medoid, function(kandidat) {
        medoid_usulan <- medoid_awal
        medoid_usulan[posisi_medoid] <- kandidat
        konfigurasi_usulan <- ringkas_konfigurasi(
          scaled_data,
          medoid_usulan
        )

        data.frame(
          Posisi_Medoid = posisi_medoid,
          Medoid_Diganti = medoid_awal[posisi_medoid],
          Kandidat = kandidat,
          Total_Biaya_Baru = konfigurasi_usulan$total_biaya,
          S = konfigurasi_usulan$total_biaya - biaya_awal,
          stringsAsFactors = FALSE
        )
      })
    )
  })
) %>%
  arrange(S, Total_Biaya_Baru, Kandidat)

pertukaran_terbaik <- hasil_pertukaran[1, ]
medoid_iterasi_1 <- medoid_awal
medoid_iterasi_1[pertukaran_terbaik$Posisi_Medoid] <-
  pertukaran_terbaik$Kandidat

konfigurasi_iterasi_1 <- ringkas_konfigurasi(
  scaled_data,
  medoid_iterasi_1
)

biaya_baru <- konfigurasi_iterasi_1$total_biaya
nilai_s_terbaik <- biaya_baru - biaya_awal

biaya_per_medoid_iterasi_1 <- tapply(
  konfigurasi_iterasi_1$biaya_objek,
  factor(
    konfigurasi_iterasi_1$medoid_terdekat,
    levels = medoid_iterasi_1
  ),
  sum
)

tabel_kandidat <- hasil_pertukaran %>%
  slice_head(n = 5) %>%
  select(
    Medoid_Diganti,
    Kandidat,
    Total_Biaya_Baru,
    S
  )

kable(
  tabel_kandidat,
  row.names = FALSE,
  digits = 6,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 10: Lima Kandidat Pertukaran dengan Nilai S Terkecil"
)
Tabel 10: Lima Kandidat Pertukaran dengan Nilai S Terkecil
Medoid_Diganti Kandidat Total_Biaya_Baru S
Aceh Sulawesi Tengah 4,983162 -0,310516
Aceh Kalimantan Tengah 5,038312 -0,255366
Aceh Sulawesi Utara 5,078223 -0,215455
Aceh Jambi 5,155749 -0,137929
Aceh Kalimantan Barat 5,241422 -0,052256

Hasil evaluasi menunjukkan bahwa Sulawesi Tengah merupakan kandidat dengan penurunan biaya terbesar dan menggantikan medoid Aceh. Dengan demikian, konfigurasi medoid usulan pada iterasi pertama menjadi Sulawesi Tengah, DKI Jakarta, Jawa Timur.

Setelah kandidat medoid baru ditentukan, jarak Manhattan seluruh provinsi dihitung kembali terhadap ketiga medoid tersebut. Setiap provinsi kemudian dialokasikan ulang kepada kandidat medoid dengan jarak paling kecil. Hasil alokasi baru secara lengkap disajikan pada Tabel 11.

jumlah_anggota_iterasi_1 <- table(factor(
  konfigurasi_iterasi_1$medoid_terdekat,
  levels = medoid_iterasi_1
))

tabel_alokasi_iterasi_1 <- buat_tabel_alokasi(
  konfigurasi_iterasi_1,
  medoid_iterasi_1
)

tampilkan_tabel_alokasi(
  tabel_alokasi_iterasi_1,
  caption = paste(
    "Tabel 11: Hasil Alokasi Provinsi terhadap Kandidat",
    "Medoid Baru pada Iterasi Pertama"
  )
)
Tabel 11: Hasil Alokasi Provinsi terhadap Kandidat Medoid Baru pada Iterasi Pertama
Klaster Medoid Anggota Jumlah Anggota
1 Sulawesi Tengah Aceh, Bali, Banten, Bengkulu, Daerah Istimewa Yogyakarta, Gorontalo, Jambi, Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Maluku, Maluku Utara, Nusa Tenggara Barat, Nusa Tenggara Timur, Papua, Papua Barat, Riau, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara, Sumatera Barat, Sumatera Selatan, Sumatera Utara 30
2 DKI Jakarta DKI Jakarta, Jawa Barat 2
3 Jawa Timur Jawa Tengah, Jawa Timur 2
Total - - 34

Berdasarkan pengalokasian ulang tersebut, klaster pertama dengan medoid Sulawesi Tengah beranggotakan 30 provinsi, klaster kedua dengan medoid DKI Jakarta beranggotakan 2 provinsi, dan klaster ketiga dengan medoid Jawa Timur beranggotakan 2 provinsi. Jumlah anggota ketiga klaster sama dengan 34 provinsi, sehingga tidak terdapat provinsi yang terlewat maupun tercatat ganda.

Rincian asal biaya setiap klaster pada konfigurasi kandidat medoid baru disajikan pada Tabel 12. Cara membacanya sama dengan konfigurasi awal, yaitu setiap nilai merupakan kontribusi jarak satu provinsi terhadap kandidat medoid terdekatnya.

tabel_biaya_iterasi_1 <- buat_tabel_biaya_klaster(
  konfigurasi_iterasi_1,
  medoid_iterasi_1
)

tampilkan_tabel_biaya(
  tabel_biaya_iterasi_1,
  caption = paste(
    "Tabel 12: Rincian Perhitungan C1, C2, dan C3",
    "pada Konfigurasi Kandidat Medoid Baru"
  )
)
Tabel 12: Rincian Perhitungan C1, C2, dan C3 pada Konfigurasi Kandidat Medoid Baru
Komponen Biaya Medoid Kontribusi Jarak Setiap Anggota Total Biaya
C1 Sulawesi Tengah Aceh (0,078444) + Bali (0,194238) + Banten (0,406188) + Bengkulu (0,080788) + Daerah Istimewa Yogyakarta (0,258250) + Gorontalo (0,111123) + Jambi (0,053564) + Kalimantan Barat (0,076755) + Kalimantan Selatan (0,068449) + Kalimantan Tengah (0,015777) + Kalimantan Timur (0,239461) + Kalimantan Utara (0,089344) + Kepulauan Bangka Belitung (0,042533) + Kepulauan Riau (0,034719) + Lampung (0,222123) + Maluku (0,098514) + Maluku Utara (0,112415) + Nusa Tenggara Barat (0,094805) + Nusa Tenggara Timur (0,085197) + Papua (0,073833) + Papua Barat (0,102066) + Riau (0,217431) + Sulawesi Barat (0,104223) + Sulawesi Selatan (0,227665) + Sulawesi Tengah (0,000000) + Sulawesi Tenggara (0,067574) + Sulawesi Utara (0,028668) + Sumatera Barat (0,107947) + Sumatera Selatan (0,182796) + Sumatera Utara (0,384523) 3,859412
C2 DKI Jakarta DKI Jakarta (0,000000) + Jawa Barat (0,954410) 0,954410
C3 Jawa Timur Jawa Tengah (0,169340) + Jawa Timur (0,000000) 0,169340

Biaya masing-masing klaster pada konfigurasi baru adalah:

\[ \begin{aligned} C_{1,\text{baru}} &=\sum_{X_i\in G_1^{\text{baru}}} d(X_i,\text{Sulawesi Tengah}) =3{,}859412, \\ C_{2,\text{baru}} &=\sum_{X_i\in G_2^{\text{baru}}} d(X_i,\text{DKI Jakarta}) =0{,}954410, \\ C_{3,\text{baru}} &=\sum_{X_i\in G_3^{\text{baru}}} d(X_i,\text{Jawa Timur}) =0{,}169340. \end{aligned} \]

Setelah jarak seluruh provinsi dihitung kembali, total biaya pada konfigurasi baru adalah:

\[ \begin{aligned} C_{\text{baru}} &=C_{1,\text{baru}}+C_{2,\text{baru}}+C_{3,\text{baru}} \\ &=3{,}859412 + 0{,}954410 + 0{,}169340 \\ &=4{,}983162. \end{aligned} \]

6.2.4 Keputusan Pertukaran pada Iterasi Pertama

Nilai total selisih biaya pertukaran dihitung sebagai berikut:

\[ \begin{aligned} S &=C_{\text{baru}}-C_{\text{awal}} \\ &=4{,}983162-5{,}293678 \\ &=-0{,}310516. \end{aligned} \]

Karena diperoleh \(S < 0\), pertukaran medoid Aceh dengan Sulawesi Tengah diterima. Konfigurasi medoid yang dihasilkan pada akhir iterasi pertama adalah Sulawesi Tengah, DKI Jakarta, Jawa Timur. Pada penerapan algoritma secara lengkap, konfigurasi tersebut selanjutnya digunakan untuk mengevaluasi kandidat pertukaran pada iterasi berikutnya. Proses dihentikan ketika tidak terdapat lagi pertukaran yang menghasilkan \(S < 0\). Ilustrasi dalam subbab ini dibatasi sampai iterasi pertama agar mekanisme pengalokasian objek dan pertukaran medoid dapat diamati secara rinci.

6.3 Pembentukan Klaster K-Medoids (PAM)

# Membentuk model PAM menggunakan jumlah klaster optimal
pam_fit_raw <- pam(scaled_data, k = k_opt, metric = "manhattan")

# Nomor klaster bawaan PAM tidak selalu berurutan dari beban rendah ke tinggi.
# Oleh karena itu, klaster diurutkan ulang berdasarkan rata-rata skor beban,
# yaitu rata-rata dari tiga variabel yang sudah dinormalisasi.
cluster_raw <- pam_fit_raw$clustering
skor_beban <- rowMeans(scaled_data)
rata_skor_raw <- tapply(skor_beban, cluster_raw, mean)
urutan_cluster_raw <- as.integer(names(sort(rata_skor_raw)))

peta_nomor_cluster <- setNames(
  seq_along(urutan_cluster_raw),
  as.character(urutan_cluster_raw)
)

cluster_final <- as.integer(
  peta_nomor_cluster[as.character(cluster_raw)]
)

# Menggabungkan hasil klaster dengan nama provinsi dan data asli
hasil_cluster <- data_cluster %>%
  mutate(
    Cluster = factor(cluster_final, levels = seq_len(k_opt))
  )

if (anyNA(hasil_cluster$Cluster)) {
  stop("Pelabelan ulang klaster tidak berhasil dilakukan.")
}

Titik medoid representatif yang menjadi pusat aktual dari masing-masing klaster adalah:

medoids_df <- data.frame(
  Cluster_Raw = as.integer(
    pam_fit_raw$clustering[pam_fit_raw$id.med]
  ),
  Provinsi_Medoid = rownames(scaled_data)[pam_fit_raw$id.med],
  stringsAsFactors = FALSE
) %>%
  mutate(
    Klaster = as.integer(
      peta_nomor_cluster[as.character(Cluster_Raw)]
    )
  ) %>%
  arrange(Klaster) %>%
  select(Klaster, Provinsi_Medoid)

if (nrow(medoids_df) != k_opt || anyNA(medoids_df$Klaster)) {
  stop("Identitas medoid tidak berhasil diselaraskan dengan label klaster akhir.")
}

kable(
  medoids_df,
  row.names = FALSE,
  caption = "Tabel 13: Titik Medoid (Pusat Aktual) Tiap Klaster"
)
Tabel 13: Titik Medoid (Pusat Aktual) Tiap Klaster
Klaster Provinsi_Medoid
1 Sulawesi Tengah
2 Jawa Barat
3 Jawa Timur

6.4 Jarak Antar-Medoid

Matriks jarak Manhattan antarmedoid mengindikasikan derajat pemisahan antarkelompok:

# Mengurutkan matriks medoid sesuai label klaster akhir
urutan_baris_medoid <- match(
  medoids_df$Provinsi_Medoid,
  rownames(pam_fit_raw$medoids)
)

medoids_matrix <- pam_fit_raw$medoids[
  urutan_baris_medoid,
  ,
  drop = FALSE
]

jarak_medoid <- as.matrix(dist(medoids_matrix, method = "manhattan"))
colnames(jarak_medoid) <- paste("Klaster", 1:k_opt)
rownames(jarak_medoid) <- paste("Klaster", 1:k_opt)

kable(
  jarak_medoid,
  digits = 4,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 14: Matriks Jarak Manhattan Antar-Medoid"
)
Tabel 14: Matriks Jarak Manhattan Antar-Medoid
Klaster 1 Klaster 2 Klaster 3
Klaster 1 0,0000 2,1716 2,1341
Klaster 2 2,1716 0,0000 0,9911
Klaster 3 2,1341 0,9911 0,0000

6.5 Rata-rata Variabel Tiap Klaster

ringkasan_klaster <- hasil_cluster %>%
  group_by(Cluster) %>%
  summarise(
    Jumlah_Provinsi = n(),
    Rata_Populasi = mean(Population),
    Rata_Total_Kasus = mean(Total_Cases),
    Rata_Kematian = mean(Total_Deaths),
    .groups = "drop"
  ) %>%
  arrange(Cluster)

if (sum(ringkasan_klaster$Jumlah_Provinsi) != nrow(data_cluster)) {
  stop("Jumlah anggota seluruh klaster tidak sama dengan jumlah provinsi.")
}

# Memberikan nama profil yang konsisten dengan urutan skor beban
if (k_opt == 2) {
  ringkasan_klaster$Profil <- c(
    "Beban Rendah-Sedang",
    "Beban Tinggi"
  )
} else if (k_opt == 3) {
  ringkasan_klaster$Profil <- c(
    "Beban Rendah-Sedang",
    "Kasus Kumulatif Tinggi",
    "Populasi dan Kematian Kumulatif Tinggi"
  )
} else {
  ringkasan_klaster$Profil <- paste(
    "Tingkat Beban",
    seq_len(k_opt)
  )
}

ringkasan_tampil <- ringkasan_klaster %>%
  select(
    Cluster,
    Profil,
    Jumlah_Provinsi,
    Rata_Populasi,
    Rata_Total_Kasus,
    Rata_Kematian
  )

kable(
  ringkasan_tampil,
  row.names = FALSE,
  digits = 2,
  format.args = list(big.mark = ".", decimal.mark = ","),
  caption = "Tabel 15: Rata-Rata Karakteristik Variabel Asli Tiap Klaster"
)
Tabel 15: Rata-Rata Karakteristik Variabel Asli Tiap Klaster
Cluster Profil Jumlah_Provinsi Rata_Populasi Rata_Total_Kasus Rata_Kematian
1 Beban Rendah-Sedang 30 4.411.165 85.772,03 2.039,67
2 Kasus Kumulatif Tinggi 2 28.003.735 1.293.121,00 15.725,00
3 Populasi dan Kematian Kumulatif Tinggi 2 38.421.548 618.977,00 32.626,50
# Objek anggota dan fungsi interpretasi digunakan kembali pada narasi,
# peta, serta kesimpulan agar seluruh bagian tetap konsisten.
anggota_klaster <- split(
  hasil_cluster$Province,
  hasil_cluster$Cluster
)

deskripsi_profil <- function(nama_profil) {
  if (nama_profil == "Beban Rendah-Sedang") {
    return(paste(
      "Kelompok ini memiliki beban kumulatif COVID-19 yang relatif lebih rendah",
      "dibandingkan klaster lainnya."
    ))
  }

  if (nama_profil == "Kasus Kumulatif Tinggi") {
    return(paste(
      "Kelompok ini ditandai oleh akumulasi kasus COVID-19 yang sangat tinggi,",
      "sehingga menggambarkan wilayah dengan beban penularan kumulatif terbesar."
    ))
  }

  if (nama_profil == "Populasi dan Kematian Kumulatif Tinggi") {
    return(paste(
      "Kelompok ini ditandai oleh jumlah penduduk yang besar dan akumulasi",
      "kematian COVID-19 yang paling tinggi."
    ))
  }

  paste(
    "Kelompok ini memiliki karakteristik tingkat beban sesuai urutan",
    "rata-rata skor hasil normalisasi."
  )
}

6.6 Profilisasi Klaster

Berdasarkan nilai rata-rata pada Tabel 15, profilisasi masing-masing klaster didefinisikan sebagai berikut:

Klaster 1 (Beban Rendah-Sedang)

  • Pusat medoid: Sulawesi Tengah.

  • Jumlah anggota: 30 provinsi.

  • Anggota klaster: Aceh, Bali, Banten, Bengkulu, Daerah Istimewa Yogyakarta, Gorontalo, Jambi, Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Maluku, Maluku Utara, Nusa Tenggara Barat, Nusa Tenggara Timur, Papua, Papua Barat, Riau, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara, Sumatera Barat, Sumatera Selatan, Sumatera Utara.

  • Karakteristik rata-rata: Populasi sebesar 4.411.165 jiwa, total kasus sebesar 85.772 kasus, dan total kematian sebesar 2.040 jiwa.

  • Interpretasi: Kelompok ini memiliki beban kumulatif COVID-19 yang relatif lebih rendah dibandingkan klaster lainnya.

Klaster 2 (Kasus Kumulatif Tinggi)

  • Pusat medoid: Jawa Barat.

  • Jumlah anggota: 2 provinsi.

  • Anggota klaster: DKI Jakarta, Jawa Barat.

  • Karakteristik rata-rata: Populasi sebesar 28.003.735 jiwa, total kasus sebesar 1.293.121 kasus, dan total kematian sebesar 15.725 jiwa.

  • Interpretasi: Kelompok ini ditandai oleh akumulasi kasus COVID-19 yang sangat tinggi, sehingga menggambarkan wilayah dengan beban penularan kumulatif terbesar.

Klaster 3 (Populasi dan Kematian Kumulatif Tinggi)

  • Pusat medoid: Jawa Timur.

  • Jumlah anggota: 2 provinsi.

  • Anggota klaster: Jawa Tengah, Jawa Timur.

  • Karakteristik rata-rata: Populasi sebesar 38.421.548 jiwa, total kasus sebesar 618.977 kasus, dan total kematian sebesar 32.627 jiwa.

  • Interpretasi: Kelompok ini ditandai oleh jumlah penduduk yang besar dan akumulasi kematian COVID-19 yang paling tinggi.

6.7 Visualisasi Peta Spasial dan Keanggotaan Klaster

Visualisasi spasial berikut memetakan sebaran geografis klaster berdasarkan koordinat setiap provinsi. Apabila package maps tersedia, poligon kepulauan Indonesia digunakan sebagai peta dasar (basemap); jika tidak tersedia, titik koordinat provinsi tetap dapat ditampilkan. Setiap klaster dibedakan menggunakan warna yang tercantum pada legenda peta.

# 1. Mengambil koordinat Longitude dan Latitude dari data provinsi
geo_prov <- covid_prov %>%
  transmute(
    Province,
    Longitude = as.numeric(Longitude),
    Latitude = as.numeric(Latitude)
  )

# 2. Menggabungkan koordinat dengan hasil klasterisasi
map_df <- hasil_cluster %>%
  left_join(geo_prov, by = "Province")

if (nrow(map_df) != nrow(hasil_cluster)) {
  stop("Jumlah baris berubah setelah penggabungan data koordinat.")
}

if (anyNA(map_df$Longitude) || anyNA(map_df$Latitude)) {
  stop("Terdapat provinsi yang tidak memiliki koordinat peta.")
}

# 3. Menyiapkan label dan warna legenda secara dinamis
label_legenda <- setNames(
  paste0(
    "Klaster ", ringkasan_klaster$Cluster,
    ": ", ringkasan_klaster$Profil,
    " (", ringkasan_klaster$Jumlah_Provinsi, " Provinsi)"
  ),
  as.character(ringkasan_klaster$Cluster)
)

if (k_opt == 2) {
  warna_klaster <- c(
    "1" = "#2ca25f",
    "2" = "#de2d26"
  )
} else {
  warna_klaster <- setNames(
    grDevices::hcl.colors(k_opt, palette = "Dark 3"),
    as.character(seq_len(k_opt))
  )
}

# 4. Membuat objek dasar peta. Jika package maps tersedia, basemap
#    kepulauan Indonesia ditampilkan. Jika tidak, titik koordinat tetap
#    dapat divisualisasikan tanpa menghentikan proses Knit.
peta_cluster <- ggplot()

if (requireNamespace("maps", quietly = TRUE)) {
  indonesia_map <- ggplot2::map_data(
    map = "world",
    region = "Indonesia"
  )

  if (nrow(indonesia_map) > 0) {
    peta_cluster <- peta_cluster +
      geom_polygon(
        data = indonesia_map,
        aes(x = long, y = lat, group = group),
        fill = "#f2f2f2",
        color = "#a6a6a6",
        linewidth = 0.3,
        inherit.aes = FALSE
      )
  }
}

# 5. Menambahkan titik provinsi
peta_cluster <- peta_cluster +
  geom_point(
    data = map_df,
    aes(x = Longitude, y = Latitude, color = Cluster),
    size = 3.6,
    alpha = 0.9
  )

# 6. Menambahkan label. ggrepel digunakan bila tersedia; jika tidak,
#    geom_text menjadi alternatif agar peta tetap berhasil dibuat.
if (requireNamespace("ggrepel", quietly = TRUE)) {
  peta_cluster <- peta_cluster +
    ggrepel::geom_text_repel(
      data = map_df,
      aes(
        x = Longitude,
        y = Latitude,
        label = Province,
        color = Cluster
      ),
      size = 2.5,
      max.overlaps = Inf,
      show.legend = FALSE,
      seed = 42
    )
} else {
  peta_cluster <- peta_cluster +
    geom_text(
      data = map_df,
      aes(
        x = Longitude,
        y = Latitude,
        label = Province,
        color = Cluster
      ),
      size = 2.3,
      vjust = -0.7,
      check_overlap = TRUE,
      show.legend = FALSE
    )
}

# 7. Menambahkan skala warna, batas peta, judul, dan tema
peta_cluster <- peta_cluster +
  scale_color_manual(
    values = warna_klaster,
    breaks = names(label_legenda),
    labels = unname(label_legenda),
    drop = FALSE
  ) +
  coord_quickmap(
    xlim = c(94, 142),
    ylim = c(-11, 7),
    expand = FALSE
  ) +
  labs(
    title = paste0(
      "Peta Sebaran Klaster Provinsi COVID-19 di Indonesia (k = ",
      k_opt,
      ")"
    ),
    subtitle = "Pemetaan Berdasarkan Variabel Populasi, Total Kasus, dan Total Kematian",
    x = "Bujur (Longitude)",
    y = "Lintang (Latitude)",
    color = "Klasifikasi Klaster"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 12, hjust = 0.5),
    plot.subtitle = element_text(size = 9, hjust = 0.5, color = "gray30"),
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

print(peta_cluster)
Gambar 3: Peta Sebaran Spasial Klaster Provinsi COVID-19 di Indonesia


Daftar rincian provinsi anggota untuk masing-masing klaster disajikan sebagai berikut:

for (i in seq_len(k_opt)) {
  medoid_i <- medoids_df$Provinsi_Medoid[medoids_df$Klaster == i]
  profil_i <- ringkasan_klaster$Profil[as.integer(ringkasan_klaster$Cluster) == i]
  anggota_i <- sort(anggota_klaster[[as.character(i)]])

  cat(paste0(
    "### **Anggota Klaster ", i,
    " – ", profil_i,
    " (Medoid: ", medoid_i,
    ") – Total ", length(anggota_i),
    " Provinsi**\n\n"
  ))

  cat(paste(anggota_i, collapse = ", "), "\n\n")
}

Anggota Klaster 1 – Beban Rendah-Sedang (Medoid: Sulawesi Tengah) – Total 30 Provinsi

Aceh, Bali, Banten, Bengkulu, Daerah Istimewa Yogyakarta, Gorontalo, Jambi, Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Maluku, Maluku Utara, Nusa Tenggara Barat, Nusa Tenggara Timur, Papua, Papua Barat, Riau, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara, Sumatera Barat, Sumatera Selatan, Sumatera Utara

Anggota Klaster 2 – Kasus Kumulatif Tinggi (Medoid: Jawa Barat) – Total 2 Provinsi

DKI Jakarta, Jawa Barat

Anggota Klaster 3 – Populasi dan Kematian Kumulatif Tinggi (Medoid: Jawa Timur) – Total 2 Provinsi

Jawa Tengah, Jawa Timur

7. Kesimpulan

Berdasarkan keseluruhan tahapan analisis klaster menggunakan algoritma Partitioning Around Medoids (PAM) terhadap data COVID-19 di 34 provinsi Indonesia, diperoleh kesimpulan sebagai berikut:

  1. Hasil Evaluasi dan Kualitas Pengelompokan:
    Metode Silhouette menunjukkan bahwa pengelompokan terbaik terbentuk pada 3 kelompok dengan skor Silhouette sebesar 0,853. Nilai tersebut menunjukkan bahwa hasil pengelompokan memiliki struktur kuat.

  2. Pembagian Kelompok Wilayah:

  • Klaster 1 (Beban Rendah-Sedang; medoid: Sulawesi Tengah): Mencakup 30 provinsi, yaitu Aceh, Bali, Banten, Bengkulu, Daerah Istimewa Yogyakarta, Gorontalo, Jambi, Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Maluku, Maluku Utara, Nusa Tenggara Barat, Nusa Tenggara Timur, Papua, Papua Barat, Riau, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara, Sumatera Barat, Sumatera Selatan, Sumatera Utara. Rata-rata penduduknya sebesar 4.411.165 jiwa, rata-rata total kasus sebesar 85.772 kasus, dan rata-rata total kematian sebesar 2.040 jiwa.

  • Klaster 2 (Kasus Kumulatif Tinggi; medoid: Jawa Barat): Mencakup 2 provinsi, yaitu DKI Jakarta, Jawa Barat. Rata-rata penduduknya sebesar 28.003.735 jiwa, rata-rata total kasus sebesar 1.293.121 kasus, dan rata-rata total kematian sebesar 15.725 jiwa.

  • Klaster 3 (Populasi dan Kematian Kumulatif Tinggi; medoid: Jawa Timur): Mencakup 2 provinsi, yaitu Jawa Tengah, Jawa Timur. Rata-rata penduduknya sebesar 38.421.548 jiwa, rata-rata total kasus sebesar 618.977 kasus, dan rata-rata total kematian sebesar 32.627 jiwa.

  1. Rekomendasi Penanganan:
    Perbedaan kondisi setiap kelompok memerlukan langkah penanganan yang disesuaikan dengan karakteristik klasternya:
  • Untuk Klaster 1 (Beban Rendah-Sedang): Prioritas diarahkan untuk mempertahankan kapasitas layanan dasar, memperkuat sistem rujukan, dan melakukan pemantauan berkala agar peningkatan kasus dapat direspons dengan cepat.

  • Untuk Klaster 2 (Kasus Kumulatif Tinggi): Prioritas diarahkan untuk memperkuat pencegahan penularan, kapasitas pemeriksaan, penelusuran kontak, dan koordinasi pengendalian mobilitas karena akumulasi kasusnya sangat tinggi.

  • Untuk Klaster 3 (Populasi dan Kematian Kumulatif Tinggi): Prioritas diarahkan untuk memprioritaskan kesiapan rumah sakit, ruang perawatan intensif, obat-obatan, serta penguatan perlindungan kelompok rentan untuk menekan risiko kematian.