TIF_E41253310/Model/clustering/kmeans.py

177 lines
8.7 KiB
Python

"""
K-Means Clustering Module
Mengelompokkan alumni berdasarkan kompetensi menggunakan algoritma K-Means Clustering.
Modul ini mencakup wrapper class untuk scikit-learn KMeans, pencarian k optimal (Elbow Method & Silhouette),
serta ringkasan statistik setiap kelompok (cluster).
"""
# Mengimpor modul Any untuk type hint fleksibel
from typing import Any
# Mengimpor modul KMeans dari pustaka scikit-learn (sklearn.cluster) untuk implementasi algoritma clustering berbasis centroid
from sklearn.cluster import KMeans
# Mengimpor fungsi silhouette_score untuk mengukur seberapa baik data terkelompok dalam cluster-nya dibandingkan cluster tetangga
from sklearn.metrics import silhouette_score
# Mengimpor pustaka numpy untuk operasi array numerik dan manipulasi matriks fitur
import numpy as np
# Mendefinisikan class KMeansClustering sebagai pembungkus (wrapper) model KMeans Scikit-Learn
class KMeansClustering:
"""K-Means Clustering wrapper: Memudahkan inisialisasi, pelatihan (fit), dan prediksi klaster."""
# Metode konstruktor class yang dijalankan saat objek pertama kali diinisialisasi
def __init__(self, n_clusters: int = 3, random_state: int = 42):
"""
Inisialisasi hyperparameter algoritma K-Means:
Args:
n_clusters: Jumlah cluster (k) yang diinginkan (default: 3)
random_state: Seed acak untuk memastikan hasil inisialisasi centroid selalu sama/reproducible (default: 42)
"""
# Menyimpan nilai jumlah cluster ke dalam atribut instance objek
self.n_clusters = n_clusters
# Menyimpan nilai seed acak ke dalam atribut instance objek
self.random_state = random_state
# Menginstansiasi objek model KMeans dari Scikit-Learn
# n_init='auto': Algoritma secara otomatis menentukan jumlah inisialisasi centroid awal berbasis heuristik Scikit-Learn modern
self.model = KMeans(
n_clusters=n_clusters,
random_state=random_state,
n_init='auto'
)
# Menyiapkan atribut untuk menyimpan label cluster dari tiap data setelah model dilatih (default None)
self.labels_ = None
# Menyiapkan atribut untuk menyimpan nilai inertia (Within-Cluster Sum of Squares) setelah dilatih (default None)
self.inertia_ = None
# Metode fit untuk melatih model K-Means pada dataset masukan X
def fit(self, X) -> 'KMeansClustering':
"""Melatih (fit) model K-Means terhadap matriks data/fitur X"""
# Menjalankan proses clustering pada matriks fitur X hingga posisi centroid konvergen
self.model.fit(X)
# Menyimpan label cluster hasil fitting (0, 1, 2, ...) ke atribut labels_
self.labels_ = self.model.labels_
# Mengambil nilai inertia (jumlah kuadrat jarak titik ke centroid terdekat) dari model terlatih
self.inertia_ = self.model.inertia_
# Mengembalikan instance objek diri sendiri (self) untuk chaining method jika diperlukan
return self
# Metode predict untuk memprediksi cluster data baru tanpa melatih ulang posisi centroid
def predict(self, X) -> Any:
"""Memprediksi label cluster untuk data baru berdasarkan centroid yang telah terbentuk"""
# Mengembalikan array numpy berisi label cluster hasil prediksi jarak Euclidean terdekat ke centroid
return self.model.predict(X)
# Metode fit_predict untuk melatih model sekaligus menghasilkan prediksi label cluster data dalam satu langkah
def fit_predict(self, X) -> Any:
"""Melatih model sekaligus menghasilkan label cluster secara langsung"""
# Menjalankan proses fitting dan langsung mengembalikan array label cluster
self.labels_ = self.model.fit_predict(X)
# Menyimpan nilai inersia akhir dari proses klastering
self.inertia_ = self.model.inertia_
# Mengembalikan array label cluster numerik
return self.labels_
# Fungsi untuk mencari nilai k optimal menggunakan kombinasi Elbow Method dan Silhouette Score
def find_optimal_k(X, k_range: range = range(2, 11)) -> dict:
"""
Mencari jumlah cluster optimal (k) menggunakan Elbow Method dan Silhouette Analysis.
Args:
X: Matriks fitur (feature matrix) yang telah melalui tahap preprocessing dan feature engineering
k_range: Rentang nilai k yang akan diuji (default: dari k=2 hingga k=10)
Returns:
Dictionary berisi daftar nilai k, inertia, dan silhouette score untuk setiap k yang diuji
"""
# Menyiapkan struktur data dictionary untuk menampung riwayat evaluasi
results = {
'k': [], # Menyimpan nilai k yang diuji
'inertia': [], # Menyimpan nilai inertia untuk grafik Elbow
'silhouette': [] # Menyimpan nilai rata-rata Silhouette Coefficient
}
# Melakukan iterasi untuk setiap nilai k dalam rentang k_range
for k in k_range:
# Menginisialisasi objek KMeans dengan jumlah cluster k saat ini (n_init='auto' sesuai standar Scikit-Learn terbaru)
kmeans = KMeans(n_clusters=k, random_state=42, n_init='auto')
# Melatih model dan memperoleh label pengelompokan cluster untuk matriks data X
labels = kmeans.fit_predict(X)
# Menambahkan nilai k saat ini ke list hasil
results['k'].append(k)
# Menambahkan nilai inertia (WCSS - Within-Cluster Sum of Squares) ke list hasil
results['inertia'].append(kmeans.inertia_)
# Silhouette score hanya dapat dihitung jika k > 1 dan terdapat lebih dari 1 kelompok cluster unik yang terbentuk
if k > 1 and len(set(labels)) > 1:
# Menghitung skor silhouette dan menambahkannya ke list hasil
results['silhouette'].append(silhouette_score(X, labels))
else:
# Jika kondisi tidak terpenuhi, set nilai skor silhouette menjadi 0
results['silhouette'].append(0)
# Mengembalikan dictionary lengkap berisi hasil pengujian seluruh nilai k
return results
# Fungsi untuk menghitung ringkasan statistik dan distribusi data di setiap kelompok klaster
def get_cluster_summary(df, labels: np.ndarray) -> dict:
"""
Mendapatkan ringkasan profil statistik setiap cluster hasil pengelompokan.
Args:
df: DataFrame pandas berisi data asli alumni sebelum ditransformasi
labels: Array numpy yang berisi label cluster dari setiap baris data
Returns:
Dictionary yang berisi jumlah data (count), persentase, dan contoh data per cluster
"""
# Membuat salinan (copy) DataFrame asli agar tidak memodifikasi data induk
df_result = df.copy()
# Menambahkan kolom baru 'cluster' yang berisi label klaster hasil prediksi K-Means
df_result['cluster'] = labels
# Menyiapkan dictionary untuk menampung ringkasan tiap cluster
summary = {}
# Melakukan perulangan untuk setiap ID klaster unik yang sudah diurutkan (0, 1, 2, ...)
for cluster_id in sorted(df_result['cluster'].unique()):
# Memfilter baris data yang masuk ke dalam ID cluster saat ini
cluster_data = df_result[df_result['cluster'] == cluster_id]
# Mengisi ringkasan informasi cluster (jumlah baris, persentase dari total data, dan 3 sampel data teratas)
summary[f'Cluster_{cluster_id}'] = {
'count': len(cluster_data), # Jumlah anggota klaster
'percentage': round(len(cluster_data) / len(df_result) * 100, 2), # Persentase terhadap total populasi
'sample_data': cluster_data.head(3).to_dict('records') # 3 sampel baris data berformat list of dict
}
# Mengembalikan dictionary ringkasan klaster
return summary
# Blok pengetesan lokal yang hanya dijalankan ketika file ini dieksekusi langsung di terminal (bukan diimpor)
if __name__ == "__main__":
# Mengimpor modul make_blobs untuk membuat dataset sintesis tiruan untuk keperluan unit testing
from sklearn.datasets import make_blobs
# Membuat 100 sampel data tiruan dengan 3 pusat klaster untuk menguji fungsionalitas modul
blob_data = make_blobs(n_samples=100, centers=3, random_state=42)
# Mengambil matriks fitur X dari data sintesis
X = blob_data[0]
# Menjalankan fungsi pencarian k optimal
results = find_optimal_k(X)
print("Elbow Method Results:")
# Menampilkan hasil evaluasi inertia dan silhouette score untuk setiap nilai k
for i in range(len(results['k'])):
print(f" K={results['k'][i]}: Inertia={results['inertia'][i]:.2f}, Silhouette={results['silhouette'][i]:.4f}")
# Menguji proses klastering akhir dengan k=3
kmeans = KMeansClustering(n_clusters=3)
# Melakukan fitting dan prediksi
labels = kmeans.fit_predict(X)
# Menampilkan label unik yang terbentuk
print(f"\nClustering complete! Labels: {np.unique(labels)}")