258 lines
9.4 KiB
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;
|
|
}
|
|
}
|