diff --git a/app/Http/Controllers/KmeansController.php b/app/Http/Controllers/KmeansController.php index fd46253..7d38616 100644 --- a/app/Http/Controllers/KmeansController.php +++ b/app/Http/Controllers/KmeansController.php @@ -9,16 +9,30 @@ class KmeansController extends Controller { + public function KMeansCuras() { - $data = Curas::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curas')->orderBy('jumlah_curas', 'asc')->get(); + $data = Curas::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curas') + ->orderBy('kecamatan_id', 'asc')->get(); + + // Hitung min dan max untuk normalisasi + $min = $data->min('jumlah_curas'); + $max = $data->max('jumlah_curas'); + + // Normalisasi jumlah_curas ke skala 0–1 + $data = $data->map(function ($item) use ($min, $max) { + $item->jumlah_curas_normalized = $max == $min + ? 1 + : round(($item->jumlah_curas - $min) / ($max - $min), 2); + return $item; + }); $maxIterasi = 100; - - $centroidManual = [0, 1, 3]; + // Centroid awal langsung dalam skala 0–1 + $centroidManual = [0.0, 0.5, 1]; $centroids = collect($centroidManual)->map(function ($value) { - return ['C' => $value]; + return ['C' => round($value, 2)]; }); $centroidAwal = $centroids->toArray(); @@ -34,11 +48,14 @@ public function KMeansCuras() $jarak = []; foreach ($centroids as $idx => $centroid) { - $dist = abs($item->jumlah_curas - $centroid['C']); - $jarak["C" . ($idx + 1)] = $dist; + $dist = abs($item->jumlah_curas_normalized - $centroid['C']); + $jarak["C" . ($idx + 1)] = round($dist, 2); } - $iterasi[$i][] = array_merge(['kecamatan_id' => $item->kecamatan_id], $jarak); + $iterasi[$i][] = array_merge([ + 'kecamatan_id' => $item->kecamatan_id, + 'normal' => round($item->jumlah_curas_normalized, 2) + ], $jarak); $minIndex = array_keys($jarak, min($jarak))[0]; $clusterNumber = (int) str_replace("C", "", $minIndex); @@ -54,20 +71,20 @@ public function KMeansCuras() $prevAssignment = $currentAssignment; - // Update centroid berdasarkan rata-rata + // Update centroid dengan rata-rata nilai normalized foreach ($clustered as $key => $group) { - $avg = collect($group)->avg('jumlah_curas'); + $avg = collect($group)->avg('jumlah_curas_normalized'); $centroids = $centroids->map(function ($item, $index) use ($key, $avg) { return $index === ($key - 1) - ? ['C' => $avg] + ? ['C' => round($avg, 2)] : $item; }); } } - // Final mapping centroid ke klaster_id + // Mapping centroid ke klaster_id $finalCentroids = $centroids->map(function ($item, $index) { - return ['index' => $index + 1, 'C' => $item['C']]; + return ['index' => $index + 1, 'C' => round($item['C'], 2)]; })->sortBy('C')->values(); $availableKlasterIDs = Klaster::orderBy('id', 'asc')->pluck('id')->values(); @@ -82,20 +99,20 @@ public function KMeansCuras() ]); } - // Format centroid awal $centroidAwalFormatted = collect($centroidAwal)->values()->map(function ($item, $index) { - return ['C' . ($index + 1) => $item['C']]; + return ['C' . ($index + 1) => round($item['C'], 2)]; }); - // Format centroid akhir $centroidAkhirFormatted = $centroids->values()->map(function ($item, $index) { - return ['C' . ($index + 1) => $item['C']]; + return ['C' . ($index + 1) => round($item['C'], 2)]; }); $hasilKMeansCuras = [ 'centroid_awal' => $centroidAwalFormatted, 'centroid_akhir' => $centroidAkhirFormatted, - 'iterasi' => $iterasi + 'iterasi' => $iterasi, + 'min' => $min, + 'max' => $max ]; file_put_contents( @@ -103,21 +120,34 @@ public function KMeansCuras() json_encode($hasilKMeansCuras, JSON_PRETTY_PRINT) ); - return redirect('/dashboard/TampilHitungCuras'); - } public function KMeansCuranmor() { - $data = Curanmor::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curanmor')->orderBy('jumlah_curanmor', 'asc')->get(); + // Ambil data awal + $data = Curanmor::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curanmor') + ->orderBy('kecamatan_id', 'asc')->get(); + + // Hitung min dan max untuk normalisasi Min-Max + $min = $data->min('jumlah_curanmor'); + $max = $data->max('jumlah_curanmor'); + + // Normalisasi jumlah_curanmor ke skala 1-100 + $data = $data->map(function ($item) use ($min, $max) { + $item->jumlah_curanmor_normalized = $max == $min + ? 1 + : round((($item->jumlah_curanmor - $min) / ($max - $min)), 2); + return $item; + }); $maxIterasi = 100; - $centroidManual = [10, 20, 30]; + // Centroid awal dalam skala 1–100 + $centroidManual = [0.2, 0.5, 0.8]; $centroids = collect($centroidManual)->map(function ($value) { - return ['C' => $value]; + return ['C' => round($value, 2)]; }); $centroidAwal = $centroids->toArray(); @@ -133,11 +163,14 @@ public function KMeansCuranmor() $jarak = []; foreach ($centroids as $idx => $centroid) { - $dist = abs($item->jumlah_curanmor - $centroid['C']); - $jarak["C" . ($idx + 1)] = $dist; + $dist = abs($item->jumlah_curanmor_normalized - $centroid['C']); + $jarak["C" . ($idx + 1)] = round($dist, 2); // Dua angka di belakang koma } - $iterasi[$i][] = array_merge(['kecamatan_id' => $item->kecamatan_id], $jarak); + $iterasi[$i][] = array_merge([ + 'kecamatan_id' => $item->kecamatan_id, + 'normal' => round($item->jumlah_curanmor_normalized, 2) + ], $jarak); $minIndex = array_keys($jarak, min($jarak))[0]; $clusterNumber = (int) str_replace("C", "", $minIndex); @@ -147,18 +180,19 @@ public function KMeansCuranmor() $currentAssignment[$item->id] = $clusterNumber; } + // Cek konvergensi if ($currentAssignment === $prevAssignment) { break; } $prevAssignment = $currentAssignment; - // Update centroid berdasarkan rata-rata + // Update centroid berdasarkan rata-rata nilai yang sudah dinormalisasi foreach ($clustered as $key => $group) { - $avg = collect($group)->avg('jumlah_curanmor'); + $avg = collect($group)->avg('jumlah_curanmor_normalized'); $centroids = $centroids->map(function ($item, $index) use ($key, $avg) { return $index === ($key - 1) - ? ['C' => $avg] + ? ['C' => round($avg, 2)] : $item; }); } @@ -166,7 +200,7 @@ public function KMeansCuranmor() // Final mapping centroid ke klaster_id $finalCentroids = $centroids->map(function ($item, $index) { - return ['index' => $index + 1, 'C' => $item['C']]; + return ['index' => $index + 1, 'C' => round($item['C'], 2)]; })->sortBy('C')->values(); $availableKlasterIDs = Klaster::orderBy('id', 'asc')->pluck('id')->values(); @@ -175,11 +209,7 @@ public function KMeansCuranmor() $centroidToKlaster[$centroid['index']] = $availableKlasterIDs[$i]; } - - // Update ke database - - - + // Update hasil clustering ke database foreach ($data as $item) { Curanmor::where('id', $item->id)->update([ 'klaster_id' => $centroidToKlaster[$item->temp_klaster], @@ -188,18 +218,20 @@ public function KMeansCuranmor() // Format centroid awal $centroidAwalFormatted = collect($centroidAwal)->values()->map(function ($item, $index) { - return ['C' . ($index + 1) => $item['C']]; + return ['C' . ($index + 1) => round($item['C'], 2)]; }); // Format centroid akhir $centroidAkhirFormatted = $centroids->values()->map(function ($item, $index) { - return ['C' . ($index + 1) => $item['C']]; + return ['C' . ($index + 1) => round($item['C'], 2)]; }); $hasilKMeansCuranmor = [ 'centroid_awal' => $centroidAwalFormatted, 'centroid_akhir' => $centroidAkhirFormatted, - 'iterasi' => $iterasi + 'iterasi' => $iterasi, + 'min' => $min, + 'max' => $max ]; file_put_contents( @@ -207,8 +239,10 @@ public function KMeansCuranmor() json_encode($hasilKMeansCuranmor, JSON_PRETTY_PRINT) ); - return redirect('/dashboard/TampilHitungCuranmor'); } + + + } diff --git a/app/Http/Controllers/LandingController.php b/app/Http/Controllers/LandingController.php index 448061b..efd3b74 100644 --- a/app/Http/Controllers/LandingController.php +++ b/app/Http/Controllers/LandingController.php @@ -25,6 +25,9 @@ public function runKmeans() $serviceKMeans = new KMeansService(); $serviceKMeans->SSEElbowCuranmor(); $serviceKMeans->SSEElbowCuras(); + $serviceKMeans->hitungDBSCANManual(); + $serviceKMeans->kmeansWithSilhouetteSingleMethod(); + $serviceKMeansCuras = new KMeansService(); $hasilKMeansCuras = $serviceKMeansCuras->hitungKMeansCuras(); diff --git a/app/Services/KMeansService.php b/app/Services/KMeansService.php index 13a8fa6..ad6d18a 100644 --- a/app/Services/KMeansService.php +++ b/app/Services/KMeansService.php @@ -5,6 +5,7 @@ use App\Models\Curas; use App\Models\Klaster; use App\Models\Curanmor; +use Illuminate\Support\Facades\Storage; class KMeansService @@ -388,6 +389,243 @@ public function SSEElbowCuras() ); } + public function hitungDBSCANManual() + { + $eps = 1.5; // Jarak maksimum antar titik + $minPts = 3; // Minimum tetangga agar jadi core point + + $data = Curas::select('jumlah_curas')->get()->pluck('jumlah_curas')->map(fn($v) => (float)$v)->toArray(); + $n = count($data); + + $visited = array_fill(0, $n, false); + $labels = array_fill(0, $n, null); + $clusterId = 0; + + // Fungsi cari tetangga + $regionQuery = function ($pointIndex) use ($data, $eps) { + $neighbors = []; + foreach ($data as $i => $val) { + if (abs($val - $data[$pointIndex]) <= $eps) { + $neighbors[] = $i; + } + } + return $neighbors; + }; + + // DBSCAN Proses + for ($i = 0; $i < $n; $i++) { + if ($visited[$i]) continue; + $visited[$i] = true; + + $neighbors = $regionQuery($i); + if (count($neighbors) < $minPts) { + $labels[$i] = -1; // noise + continue; + } + + $labels[$i] = $clusterId; + $seeds = array_diff($neighbors, [$i]); + + foreach ($seeds as $seed) { + if (!$visited[$seed]) { + $visited[$seed] = true; + $newNeighbors = $regionQuery($seed); + if (count($newNeighbors) >= $minPts) { + $seeds = array_unique(array_merge($seeds, $newNeighbors)); + } + } + + if ($labels[$seed] === null || $labels[$seed] === -1) { + $labels[$seed] = $clusterId; + } + } + + $clusterId++; + } + + // Hitung silhouette (manual satu dimensi) + $silhouetteScores = []; + foreach ($data as $i => $val) { + $label = $labels[$i]; + if ($label === -1) continue; // skip noise + + $sameCluster = []; + $otherClusters = []; + + foreach ($data as $j => $otherVal) { + if ($i === $j || $labels[$j] === -1) continue; + if ($labels[$j] === $label) { + $sameCluster[] = abs($val - $otherVal); + } else { + $otherClusters[$labels[$j]][] = abs($val - $otherVal); + } + } + + $a = count($sameCluster) > 0 ? array_sum($sameCluster) / count($sameCluster) : 0; + $b = count($otherClusters) > 0 ? min(array_map(fn($d) => array_sum($d) / count($d), $otherClusters)) : 0; + + $s = ($a === $b && $a === 0) ? 0 : ($b - $a) / max($a, $b); + $silhouetteScores[] = $s; + } + + $meanSilhouette = count($silhouetteScores) > 0 ? round(array_sum($silhouetteScores) / count($silhouetteScores), 4) : 0; + + // Susun hasil anggota klaster + $anggotaKlaster = []; + foreach ($labels as $i => $label) { + $anggotaKlaster[$label][] = $data[$i]; + } + + $jumlahKlaster = count(array_filter(array_keys($anggotaKlaster), fn($k) => $k !== -1)); + + $hasil = [ + 'silhouette' => $meanSilhouette, + 'jumlah_klaster' => $jumlahKlaster, + 'anggota_klaster' => $anggotaKlaster, + ]; + + file_put_contents( + storage_path('app/public/dbscan_curas.json'), + json_encode($hasil, JSON_PRETTY_PRINT) + ); + return $hasil; + } + + public function kmeansWithSilhouetteSingleMethod(int $k = 2, int $maxIter = 100): array + { + // Ambil data dari tabel curas + $data = \App\Models\Curas::select('kecamatan_id', 'jumlah_curas') + ->get() + ->pluck('jumlah_curas', 'kecamatan_id') + ->toArray(); + + $ids = array_keys($data); + $values = array_values($data); + + // Inisialisasi centroid secara random + // Ambil nilai min dan max dari data + $minValue = min($values); + $maxValue = max($values); + + $centroids = []; + + if ($k == 1) { + // Jika cuma 1 klaster, centroid random antara min dan max + $centroids[] = rand($minValue, $maxValue); + } else { + // Untuk k >= 2, inisialisasi centroid unik integer dalam rentang min-max + $centroids = []; + while (count($centroids) < $k) { + $randCentroid = rand($minValue, $maxValue); + if (!in_array($randCentroid, $centroids)) { + $centroids[] = $randCentroid; + } + } + } + + + $clusters = []; + $iter = 0; + + do { + $clusters = array_fill(0, $k, []); + + // Assign ke klaster terdekat (jarak absolut 1 dimensi) + foreach ($values as $idx => $val) { + $distances = []; + foreach ($centroids as $cidx => $centroid) { + $distances[$cidx] = abs($val - $centroid); + } + asort($distances); + $nearestCluster = key($distances); + $clusters[$nearestCluster][] = $idx; + } + + // Hitung centroid baru + $newCentroids = []; + foreach ($clusters as $cluster) { + if (count($cluster) > 0) { + $sum = 0; + foreach ($cluster as $i) { + $sum += $values[$i]; + } + $newCentroids[] = $sum / count($cluster); + } else { + // Jika cluster kosong, pilih centroid random + $newCentroids[] = $values[array_rand($values)]; + } + } + + $iter++; + if ($newCentroids === $centroids) break; + if ($iter >= $maxIter) break; + $centroids = $newCentroids; + + } while (true); + + // Fungsi hitung rata-rata jarak + $avgDistance = function(int $i, array $cluster) use ($values): float { + if (count($cluster) <= 1) return 0; + $sum = 0; + foreach ($cluster as $idx) { + if ($idx == $i) continue; + $sum += abs($values[$i] - $values[$idx]); + } + return $sum / (count($cluster) - 1); + }; + + // Hitung silhouette untuk setiap data + $silhouetteScores = []; + for ($i = 0; $i < count($values); $i++) { + // Cari klaster data ke-i + $clusterIdx = null; + foreach ($clusters as $cidx => $cluster) { + if (in_array($i, $cluster)) { + $clusterIdx = $cidx; + break; + } + } + + $a = $avgDistance($i, $clusters[$clusterIdx]); + $b = INF; + foreach ($clusters as $cidx => $cluster) { + if ($cidx == $clusterIdx) continue; + $dist = $avgDistance($i, $cluster); + if ($dist < $b) $b = $dist; + } + + $silhouetteScores[] = ($b - $a) / max($a, $b); + } + + $silhouette = array_sum($silhouetteScores) / count($silhouetteScores); + + // Bentuk output anggota klaster dengan kecamatan_id + $clusterMembers = []; + foreach ($clusters as $cidx => $cluster) { + $clusterMembers[$cidx] = []; + foreach ($cluster as $idx) { + $clusterMembers[$cidx][] = $ids[$idx]; + } + } + + $hasil = [ + 'silhouette' => $silhouette, + 'jumlah_klaster' => $k, + 'anggota_klaster' => $clusterMembers, + 'iterasi' => $iter, + 'centroid' => $centroids, + ]; + + // Simpan ke file JSON + file_put_contents( + storage_path('app/public/silhoute_kmeans_curas.json'), + json_encode($hasil, JSON_PRETTY_PRINT) + ); + + return $hasil; + } + + diff --git a/database/seeders/CuranmorSeeder.php b/database/seeders/CuranmorSeeder.php index d7e3963..054e118 100644 --- a/database/seeders/CuranmorSeeder.php +++ b/database/seeders/CuranmorSeeder.php @@ -19,28 +19,28 @@ public function run(): void // Data jumlah curanmor untuk setiap kecamatan $dataCuranmor= [ - 1 => 5, - 2 => 4, - 3 => 2, - 4 => 22, - 5 => 4, - 6 => 18, - 7 => 0, - 8 => 37, - 9 => 9, - 10 => 3, - 11 => 2, - 12 => 13, - 13 => 1, - 14 => 21, - 15 => 14, - 16 => 4, - 17 => 10, - 18 => 0, - 19 => 1, + 1 => 11, + 2 => 14, + 3 => 21, + 4 => 59, + 5 => 8, + 6 => 42, + 7 => 13, + 8 => 188, + 9 => 30, + 10 => 13, + 11 => 4, + 12 => 33, + 13 => 3, + 14 => 48, + 15 => 53, + 16 => 12, + 17 => 30, + 18 => 5, + 19 => 4, 20 => 10, - 21 => 1, - 22 => 2, + 21 => 5, + 22 => 7, 23 => 15, 24 => 4, ]; diff --git a/database/seeders/CurasSeeder.php b/database/seeders/CurasSeeder.php index 97bcb6a..2ca816e 100644 --- a/database/seeders/CurasSeeder.php +++ b/database/seeders/CurasSeeder.php @@ -17,20 +17,20 @@ public function run(): void $dataCuras = [ 1 => 0, - 2 => 0, - 3 => 0, - 4 => 0, + 2 => 1, + 3 => 2, + 4 => 1, 5 => 1, 6 => 1, 7 => 0, - 8 => 0, - 9 => 0, + 8 => 1, + 9 => 1, 10 => 0, 11 => 0, 12 => 0, 13 => 0, 14 => 0, - 15 => 0, + 15 => 1, 16 => 0, 17 => 0, 18 => 0, diff --git a/public/assets/assetLanding/images/team/zahra.jpg b/public/assets/assetLanding/images/team/zahra.jpg new file mode 100644 index 0000000..c45d3b2 Binary files /dev/null and b/public/assets/assetLanding/images/team/zahra.jpg differ diff --git a/resources/views/landing.blade.php b/resources/views/landing.blade.php index 04b9229..f41b3bd 100644 --- a/resources/views/landing.blade.php +++ b/resources/views/landing.blade.php @@ -1434,6 +1434,47 @@ class="img-fluid rounded-circle" +
+
+
+ image +
+
+ +
+
+ Fatimatuzzahra
+
+ ANGGOTA PENGUJI +
+
+
+

+ Dosen Politeknik Negeri Jember Program Studi D4 Teknik Informatika. Berperan sebagai Anggota Penguji pada Web SIG Pemetaan Daerah Rawan Curas dan Curanmor +

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+