MIF_E31212366/app/Http/Controllers/KMeansController.php

258 lines
9.4 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\DataDBD;
use App\Models\Kecamatan;
use App\Charts\MonthlyDBDChart;
use App\Charts\YearlyDBDChart;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
class KMeansController extends Controller
{
public function dashboard(YearlyDBDChart $yearlyChart, Request $request)
{
// Ambil daftar tahun yang ada di database
$availableYears = DataDBD::select('tahun')->distinct()->orderBy('tahun')->pluck('tahun');
// Jika tidak ada tahun yang dipilih, gunakan tahun pertama yang tersedia
$selectedYear = $request->input('year', $availableYears->first());
// Ambil data jumlah kasus DBD dari database untuk tahun tertentu
$dataByYear = DataDBD::where('tahun', $selectedYear)->get();
// Buat objek MonthlyDBDChart dengan memberikan data dan tahun yang dipilih
$monthlyChart = new MonthlyDBDChart($dataByYear, $selectedYear);
// Bangun chart untuk jumlah kasus DBD setiap bulannya
$monthlyChartData = $monthlyChart->build();
// // Bangun chart untuk jumlah kasus DBD setiap tahunnya
$yearlyChartData = $yearlyChart->build();
$clusterResults = Session::get('clusterResults');
// Kembalikan view dengan chart yang dibuat dan daftar tahun yang tersedia
return view('dashboard', [
'clusterResults' => $clusterResults,
'monthlyChart' => $monthlyChartData,
'yearlyChart' => $yearlyChartData,
'selectedYear' => $selectedYear,
'availableYears' => $availableYears // Sertakan daftar tahun yang tersedia
]);
}
public function index()
{
// Mengambil data dari tabel DataDBD
$data = DataDBD::all();
$kecamatanData = Kecamatan::all()->keyBy('id')->toArray();
// Menggabungkan jumlah kasus dan jumlah penduduk untuk kecamatan yang sama dari semua tahun
$dataset = [];
foreach ($data as $d) {
$key = $d->id_kecamatan;
if (!isset($dataset[$key])) {
$dataset[$key] = [
'id_kecamatan' => $d->id_kecamatan,
'nama_kecamatan' => $kecamatanData[$d->id_kecamatan]['nama_kecamatan'] ?? 'Unknown',
'latitude' => $kecamatanData[$d->id_kecamatan]['latitude'] ?? null,
'longitude' => $kecamatanData[$d->id_kecamatan]['longitude'] ?? null,
'jumlah_penduduk' => (int) $kecamatanData[$d->id_kecamatan]['jumlah_penduduk'],
'jumlah_kasus' => (int) $d->jumlah_kasus,
'cases_per_capita' => (int) $d->jumlah_kasus / max((int) $kecamatanData[$d->id_kecamatan]['jumlah_penduduk'], 1) // Prevent division by zero
];
} else {
$dataset[$key]['jumlah_kasus'] += (int) $d->jumlah_kasus;
$dataset[$key]['cases_per_capita'] = $dataset[$key]['jumlah_kasus'] / max((int) $dataset[$key]['jumlah_penduduk'], 1); // Prevent division by zero
}
}
// Mengubah array asosiatif menjadi array numerik
$dataset = array_values($dataset);
// Menjalankan algoritma K-Means
$k = 3;
$result = $this->kMeans($dataset, $k);
// Menghitung tingkat kasus berdasarkan perhitungan statistik
$caseLevels = $this->calculateCaseLevels($dataset);
// Mengelompokkan hasil clustering berdasarkan kecamatan
$clusterResults = [];
foreach ($result['clusters'] as $clusterIndex => $cluster) {
foreach ($cluster as $datum) {
$clusterResults[$datum['id_kecamatan']] = [
'id_kecamatan' => $datum['id_kecamatan'],
'nama_kecamatan' => $datum['nama_kecamatan'],
'latitude' => $datum['latitude'],
'longitude' => $datum['longitude'],
'cluster' => $clusterIndex + 1,
'cases_per_capita' => $datum['cases_per_capita'],
'jumlah_penduduk' => $datum['jumlah_penduduk'],
'jumlah_kasus' => $datum['jumlah_kasus'],
'tingkat_kasus' => $caseLevels[$datum['id_kecamatan']]
];
}
}
Session::put('clusterResults', $clusterResults);
// Menampilkan hasil perhitungan K-Means
return view('kmeans.index', [
'steps' => $result['steps'],
'clusterResults' => $clusterResults,
'kecamatanData' => $kecamatanData
]);
}
private function calculateCaseLevels($dataset)
{
$casesPerCapita = array_column($dataset, 'cases_per_capita');
$mean = array_sum($casesPerCapita) / count($casesPerCapita);
$stdDev = sqrt(array_sum(array_map(fn($x) => pow($x - $mean, 2), $casesPerCapita)) / count($casesPerCapita));
$thresholdLow = $mean - $stdDev;
$thresholdHigh = $mean + $stdDev;
$caseLevels = [];
foreach ($dataset as $data) {
if ($data['cases_per_capita'] <= $thresholdLow) {
$caseLevels[$data['id_kecamatan']] = 'Rendah';
} elseif ($data['cases_per_capita'] >= $thresholdHigh) {
$caseLevels[$data['id_kecamatan']] = 'Tinggi';
} else {
$caseLevels[$data['id_kecamatan']] = 'Sedang';
}
}
return $caseLevels;
}
private function kMeans($data, $k)
{
// Inisialisasi centroids secara acak
$centroids = [];
$usedKecamatanIds = [];
// Array untuk menyimpan ID kecamatan yang sudah digunakan sebagai centroid
for ($i = 0; $i < $k; $i++) {
// Pilih kecamatan secara acak
do {
$randomIndex = array_rand($data);
$randomKecamatanId = $data[$randomIndex]['id_kecamatan'];
} while (in_array($randomKecamatanId, $usedKecamatanIds));
// Periksa apakah kecamatan sudah digunakan sebagai centroid sebelumnya
// Tambahkan kecamatan ke array centroid dan array kecamatan yang sudah digunakan
$centroids[$i] = $data[$randomIndex];
$usedKecamatanIds[] = $randomKecamatanId;
}
$iterations = 100;
$steps = [];
for ($i = 0; $i < $iterations; $i++) {
$clusters = array_fill(0, $k, []);
// Menempatkan setiap data ke klaster terdekat
foreach ($data as $datum) {
$distances = [];
foreach ($centroids as $centroid) {
$distances[] = $this->euclideanDistance($datum, $centroid);
}
$cluster = array_keys($distances, min($distances))[0];
$clusters[$cluster][] = $datum;
}
// Menyimpan langkah perhitungan
$steps[] = [
'iteration' => $i + 1,
'centroids' => $centroids,
'clusters' => $clusters
];
// Menghitung centroid baru
$newCentroids = [];
foreach ($clusters as $cluster => $clusterData) {
$newCentroids[$cluster] = $this->calculateCentroid($clusterData);
}
// Memeriksa konvergensi
if ($this->centroidsConverged($centroids, $newCentroids)) {
break;
}
// Memperbarui centroid untuk iterasi berikutnya
$centroids = $newCentroids;
}
return ['clusters' => $clusters, 'steps' => $steps];
}
private function euclideanDistance($datum1, $datum2)
{
return sqrt(
pow($datum1['jumlah_penduduk'] - $datum2['jumlah_penduduk'], 2) +
pow($datum1['jumlah_kasus'] - $datum2['jumlah_kasus'], 2)
);
}
private function calculateCentroid($clusterData)
{
$centroid = [
'id_kecamatan' => null,
'nama_kecamatan' => '',
'jumlah_penduduk' => 0,
'jumlah_kasus' => 0
];
$count = count($clusterData);
// Pastikan jumlah data tidak nol sebelum melakukan pembagian
if ($count > 0) {
foreach ($clusterData as $data) {
$centroid['jumlah_penduduk'] += (int) $data['jumlah_penduduk'];
$centroid['jumlah_kasus'] += (int) $data['jumlah_kasus'];
}
$centroid['jumlah_penduduk'] /= $count;
$centroid['jumlah_kasus'] /= $count;
// Mengatur id_kecamatan dari salah satu data terdekat ke centroid baru
$closestDatum = $this->getClosestDatum($centroid, $clusterData);
$centroid['id_kecamatan'] = $closestDatum['id_kecamatan'];
$centroid['nama_kecamatan'] = $closestDatum['nama_kecamatan'];
}
return $centroid;
}
private function getClosestDatum($centroid, $clusterData)
{
$minDistance = PHP_FLOAT_MAX;
$closestDatum = null;
foreach ($clusterData as $datum) {
$distance = $this->euclideanDistance($centroid, $datum);
if ($distance < $minDistance) {
$minDistance = $distance;
$closestDatum = $datum;
}
}
return $closestDatum;
}
private function centroidsConverged($centroids, $newCentroids)
{
foreach ($centroids as $key => $centroid) {
if ($this->euclideanDistance($centroid, $newCentroids[$key]) > 0.0001) {
return false;
}
}
return true;
}
}