update k-means random bukan uniq lagi

This commit is contained in:
daffarahman11 2025-05-10 23:59:44 +07:00
parent 7b20b29157
commit ae72f2717e
6 changed files with 116 additions and 226 deletions

View File

@ -5,10 +5,11 @@
use App\Models\Klaster; use App\Models\Klaster;
use App\Models\Curanmor; use App\Models\Curanmor;
use App\Models\Kecamatan; use App\Models\Kecamatan;
use App\Models\Detail_Curanmor;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Detail_Curanmor;
use App\Services\KMeansService; use App\Services\KMeansService;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
class CuranmorController extends Controller class CuranmorController extends Controller
{ {
@ -35,12 +36,13 @@ public function create()
*/ */
public function store(Request $request) public function store(Request $request)
{ {
try{
$request->validate([ $request->validate([
'kecamatan_id' => 'required|exists:kecamatans,id', 'kecamatan_id' => 'required|exists:kecamatans,id',
'jumlah_curanmor' => 'required|numeric', 'jumlah_curanmor' => 'required|numeric',
]); ]);
try{
DB::beginTransaction();
$kecamatan_id = $request->kecamatan_id; $kecamatan_id = $request->kecamatan_id;
$tambahan_curanmor = $request->jumlah_curanmor; $tambahan_curanmor = $request->jumlah_curanmor;
@ -75,18 +77,11 @@ public function store(Request $request)
$serviceSSECuranmor = new KMeansService(); $serviceSSECuranmor = new KMeansService();
$serviceSSECuranmor->SSEElbowCuranmor(); $serviceSSECuranmor->SSEElbowCuranmor();
// =====CODE TAMBAH SEBELUMNYA========= DB::commit();
// $validateData = $request->validate([
// 'kecamatan_id' =>'required|max:255|exists:kecamatans,id|unique:curanmors,kecamatan_id',
// 'jumlah_curanmor' =>'required',
// 'klaster_id' =>'required|max:255|exists:klasters,id',
// ]);
// Curanmor::create($validateData);
return redirect('/dashboard/curanmor')->with('succes', 'Berhasil Menambahkan Data Curanmor Baru'); return redirect('/dashboard/curanmor')->with('succes', 'Berhasil Menambahkan Data Curanmor Baru');
}catch (\Exception $e){ }catch (\Exception $e){
return redirect('/dashboard/curanmor')->with('error', 'Gagal Menambahkan Data Curanmor Baru'); DB::rollBack();
return redirect('/dashboard/curanmor')->with('error', 'Gagal Menambahkan Data Curanmor Baru '. $e->getMessage());
} }
} }
@ -103,18 +98,6 @@ public function show(Curanmor $curanmor)
*/ */
public function edit($curanmor) public function edit($curanmor)
{ {
try {
$edit = Curanmor::find($curanmor);
return view('admin.dashboardEditCuranmor', [
'curanmor' => $edit,
'kecamatans' => Kecamatan::all(),
'klasters' => Klaster::all(),
]);
} catch (\Exception $e) {
abort(404);
}
} }
/** /**
@ -122,39 +105,7 @@ public function edit($curanmor)
*/ */
public function update(Request $request, Curanmor $curanmor) public function update(Request $request, Curanmor $curanmor)
{ {
try {
// Validasi input
$request->validate([
'kecamatan_id' => [
'required',
'exists:kecamatans,id',
Rule::unique('curanmors')->ignore($curanmor->id),
],
'klaster_id' => 'required|exists:klasters,id',
'jumlah_curanmor' => 'required|numeric|min:0',
]);
// Update data
$curanmor->update([
'kecamatan_id' => $request->kecamatan_id,
'klaster_id' => $request->klaster_id,
'jumlah_curanmor' => $request->jumlah_curanmor,
]);
$service = new KMeansService();
$hasil = $service->hitungKMeansCuranmor();
// simpan hasil ke file json
file_put_contents(storage_path('app/public/hasil_kmeans_curanmor.json'), json_encode($hasil));
$serviceSSECuranmor = new KMeansService();
$serviceSSECuranmor->SSEElbowCuranmor();
return redirect('/dashboard/curanmor')->with('succes', 'Data Kecamatan Berhasil Diubah');
} catch (\Exception $e) {
return redirect('/dashboard/curanmor')->with('error', 'Data Kecamatan Gagal Diubah: ' . $e->getMessage());
}
} }
/** /**
@ -162,25 +113,6 @@ public function update(Request $request, Curanmor $curanmor)
*/ */
public function destroy($curanmor) public function destroy($curanmor)
{ {
try {
// Cari data berdasarkan ID
$hapus = Curanmor::find($curanmor);
// Pastikan data ditemukan sebelum menghapus
if (!$hapus) {
return redirect('/dashboard/curanmor')->with('error', 'Data tidak ditemukan.');
}
// Hapus data
$hapus->delete();
$serviceSSECuranmor = new KMeansService();
$serviceSSECuranmor->SSEElbowCuranmor();
return redirect('/dashboard/curanmor')->with('succes', 'Data Curanmor Berhasil Dihapus');
} catch (\Exception $e) {
return redirect('/dashboard/curanmor')->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
} }
} }

View File

@ -9,6 +9,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\KMeansService; use App\Services\KMeansService;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
class CurasController extends Controller class CurasController extends Controller
{ {
@ -36,12 +37,13 @@ public function create()
*/ */
public function store(Request $request) public function store(Request $request)
{ {
try{
$request->validate([ $request->validate([
'kecamatan_id' => 'required|exists:kecamatans,id', 'kecamatan_id' => 'required|exists:kecamatans,id',
'jumlah_curas' => 'required|numeric', 'jumlah_curas' => 'required|numeric',
]); ]);
try{
DB::beginTransaction();
$kecamatan_id = $request->kecamatan_id; $kecamatan_id = $request->kecamatan_id;
$tambahan_curas = $request->jumlah_curas; $tambahan_curas = $request->jumlah_curas;
@ -75,15 +77,18 @@ public function store(Request $request)
$service = new KMeansService(); $service = new KMeansService();
$hasil = $service->hitungKMeansCuras(); $hasil = $service->hitungKMeansCuras();
// simpan hasil ke file json
file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil)); file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil));
$serviceSSECuras = new KMeansService(); $serviceSSECuras = new KMeansService();
$serviceSSECuras->SSEElbowCuras(); $serviceSSECuras->SSEElbowCuras();
DB::commit();
return redirect('/dashboard/curas')->with('succes', 'Data curas berhasil ditambahkan.'); return redirect('/dashboard/curas')->with('succes', 'Data curas berhasil ditambahkan.');
}catch (\Exception $e){ }catch (\Exception $e){
return redirect('/dashboard/curas')->with('error', 'Gagal Menambahkan Data Curas Baru');
DB::rollBack();
return redirect('/dashboard/curas')->with('error', 'Gagal Menambahkan Data Curas Baru'. $e->getMessage());
} }
} }
@ -100,18 +105,6 @@ public function show(Curas $curas)
*/ */
public function edit($curas) public function edit($curas)
{ {
try {
$edit = Curas::find($curas);
return view('admin.dashboardEditCuras', [
'curas' => $edit,
'kecamatans' => Kecamatan::all(),
'klasters' => Klaster::all(),
]);
} catch (\Exception $e) {
abort(404);
}
} }
/** /**
@ -119,71 +112,13 @@ public function edit($curas)
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
try {
// Cari data berdasarkan ID yang dikirim
$curas = Curas::findOrFail($id);
// Debugging untuk memastikan data ditemukan
// dd($curas->toArray()); // Jika berhasil, ini akan menampilkan data curas
// Validasi input
$request->validate([
'kecamatan_id' => [
'required',
'exists:kecamatans,id',
Rule::unique('curas')->ignore($curas->id),
],
'klaster_id' => 'required|exists:klasters,id',
'jumlah_curas' => 'required|integer|min:0',
]);
// Update data
$curas->update([
'kecamatan_id' => $request->kecamatan_id,
'klaster_id' => $request->klaster_id,
'jumlah_curas' => $request->jumlah_curas,
]);
$service = new KMeansService();
$hasil = $service->hitungKMeansCuras();
// simpan hasil ke file json
file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil));
$serviceSSECuras = new KMeansService();
$serviceSSECuras->SSEElbowCuras();
return redirect('/dashboard/curas')->with('succes', 'Data Kecamatan Berhasil Diubah');
} catch (\Exception $e) {
return redirect('/dashboard/curas')->with('error', 'Data Kecamatan Gagal Diubah: ' . $e->getMessage());
} }
}
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
*/ */
public function destroy($curas) public function destroy($curas)
{ {
try {
// Cari data berdasarkan ID
$hapus = Curas::find($curas);
// Pastikan data ditemukan sebelum menghapus
if (!$hapus) {
return redirect('/dashboard/curas')->with('error', 'Data tidak ditemukan.');
}
// Hapus data
$hapus->delete();
return redirect('/dashboard/curas')->with('succes', 'Data Curas Berhasil Dihapus');
} catch (\Exception $e) {
return redirect('/dashboard/curas')->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
} }

View File

@ -6,6 +6,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Detail_Curanmor; use App\Models\Detail_Curanmor;
use App\Services\KMeansService; use App\Services\KMeansService;
use Illuminate\Support\Facades\DB;
class DetailCuranmorController extends Controller class DetailCuranmorController extends Controller
{ {
@ -65,6 +66,8 @@ public function update(Request $request, Detail_Curanmor $detail_Curanmor)
public function destroy($id) public function destroy($id)
{ {
try { try {
DB::beginTransaction();
$detail = Detail_Curanmor::findOrFail($id); $detail = Detail_Curanmor::findOrFail($id);
$curanmor = Curanmor::findOrFail($detail->curanmor_id); $curanmor = Curanmor::findOrFail($detail->curanmor_id);
@ -89,8 +92,12 @@ public function destroy($id)
$serviceSSECuranmor = new KMeansService(); $serviceSSECuranmor = new KMeansService();
$serviceSSECuranmor->SSEElbowCuranmor(); $serviceSSECuranmor->SSEElbowCuranmor();
DB::commit();
return redirect('/dashboard/detail-curanmor')->with('succes', 'Data berhasil dihapus dan curanmor diperbarui.'); return redirect('/dashboard/detail-curanmor')->with('succes', 'Data berhasil dihapus dan curanmor diperbarui.');
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollback();
return redirect('/dashboard/detail-curanmor')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage()); return redirect('/dashboard/detail-curanmor')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage());
} }
} }

View File

@ -6,6 +6,7 @@
use App\Models\Detail_Curas; use App\Models\Detail_Curas;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\KMeansService; use App\Services\KMeansService;
use Illuminate\Support\Facades\DB;
class DetailCurasController extends Controller class DetailCurasController extends Controller
{ {
@ -65,6 +66,9 @@ public function update(Request $request, Detail_Curas $detail_Curas)
public function destroy($id) public function destroy($id)
{ {
try { try {
DB::beginTransaction();
$detail = Detail_Curas::findOrFail($id); $detail = Detail_Curas::findOrFail($id);
// Ambil curas terkait // Ambil curas terkait
@ -92,8 +96,12 @@ public function destroy($id)
$serviceSSECuras = new KMeansService(); $serviceSSECuras = new KMeansService();
$serviceSSECuras->SSEElbowCuras(); $serviceSSECuras->SSEElbowCuras();
DB::commit();
return redirect('/dashboard/detail-curas')->with('succes', 'Data berhasil dihapus dan curas diperbarui.'); return redirect('/dashboard/detail-curas')->with('succes', 'Data berhasil dihapus dan curas diperbarui.');
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack();
return redirect('/dashboard/detail-curas')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage()); return redirect('/dashboard/detail-curas')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage());
} }

View File

@ -4,6 +4,7 @@
use App\Models\Klaster; use App\Models\Klaster;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\KMeansService;
class KlasterController extends Controller class KlasterController extends Controller
{ {
@ -35,11 +36,24 @@ public function store(Request $request)
'warna' =>'required|max:255', 'warna' =>'required|max:255',
]); ]);
$serviceKMeans = new KMeansService();
$serviceKMeans->SSEElbowCuranmor();
$serviceKMeans->SSEElbowCuras();
$serviceKMeansCuras = new KMeansService();
$hasilKMeansCuras = $serviceKMeansCuras->hitungKMeansCuras();
file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasilKMeansCuras));
$serviceKmeansCuranmor = new KMeansService();
$hasilKMeansCuranmor = $serviceKmeansCuranmor->hitungKMeansCuranmor();
file_put_contents(storage_path('app/public/hasil_kmeans_curanmor.json'), json_encode($hasilKMeansCuranmor));
Klaster::create($validateData); Klaster::create($validateData);
return redirect('/dashboard/klaster')->with('succes', 'Berhasil Menambahkan Klaster Baru'); return redirect('/dashboard/klaster')->with('succes', 'Berhasil Menambahkan Klaster Baru');
}catch (\Exception $e){ }catch (\Exception $e){
return redirect('/dashboard/klaster')->with('error', 'Gagal Menambahkan Klaster Baru'); return redirect('/dashboard/klaster')->with('error', 'Gagal Menambahkan Klaster Baru ' .$e->getMessage());
} }
} }

View File

@ -12,27 +12,24 @@ class KMeansService
public function hitungKMeansCuras() public function hitungKMeansCuras()
{ {
$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('jumlah_curas', 'asc')->get();
$k = Klaster::count('id'); $k = Klaster::count('id');
$maxIterasi = 100; $maxIterasi = 100;
$uniqueCount = $data->unique('jumlah_curas')->count(); // Ambil centroid awal yang unik
if ($uniqueCount < $k) { $minValue = $data->min('jumlah_curas');
throw new \Exception("Jumlah nilai unik pada 'jumlah_curas' ($uniqueCount) kurang dari jumlah klaster ($k). Pastikan data memiliki variasi yang cukup."); $maxValue = $data->max('jumlah_curas');
$generated = collect();
while ($generated->count() < $k) {
$randomFloat = round(mt_rand($minValue * 100, $maxValue * 100) / 100, 2);
if (!$generated->contains($randomFloat)) {
$generated->push($randomFloat);
}
} }
// Ambil centroid awal yang unik $centroids = $generated->map(function ($value) {
$centroids = $data->unique('jumlah_curas') // Pastikan nilai unik return ['jumlah_curas' => $value];
->shuffle() // Acak
->take($k) // Ambil k data
->values()
->map(function ($item) {
return [
'jumlah_curas' => $item->jumlah_curas,
];
}); });
// Simpan centroid awal sebelum iterasi
$centroidAwal = $centroids->toArray(); $centroidAwal = $centroids->toArray();
$iterasi = []; $iterasi = [];
@ -52,7 +49,7 @@ public function hitungKMeansCuras()
$iterasi[$i][] = array_merge(['kecamatan_id' => $item->kecamatan_id], $jarak); $iterasi[$i][] = array_merge(['kecamatan_id' => $item->kecamatan_id], $jarak);
$minIndex = array_keys($jarak, min($jarak))[0]; // e.g. "jarakC2" $minIndex = array_keys($jarak, min($jarak))[0];
$clusterNumber = (int) str_replace("C", "", $minIndex); $clusterNumber = (int) str_replace("C", "", $minIndex);
$clustered[$clusterNumber][] = $item; $clustered[$clusterNumber][] = $item;
@ -60,15 +57,12 @@ public function hitungKMeansCuras()
$currentAssignment[$item->id] = $clusterNumber; $currentAssignment[$item->id] = $clusterNumber;
} }
// ✨ Cek konvergensi: jika assignment sekarang == sebelumnya, break
if ($currentAssignment === $prevAssignment) { if ($currentAssignment === $prevAssignment) {
break; break;
} }
$prevAssignment = $currentAssignment; $prevAssignment = $currentAssignment;
// Update centroid berdasarkan rata-rata // Update centroid berdasarkan rata-rata
foreach ($clustered as $key => $group) { foreach ($clustered as $key => $group) {
$avg = collect($group)->avg('jumlah_curas'); $avg = collect($group)->avg('jumlah_curas');
@ -80,20 +74,16 @@ public function hitungKMeansCuras()
} }
} }
// Final mapping centroid ke klaster_id
// Final mapping centroid ke klaster_id (aman/sedang/rawan)
$finalCentroids = $centroids->map(function ($item, $index) { $finalCentroids = $centroids->map(function ($item, $index) {
return ['index' => $index + 1, 'jumlah_curas' => $item['jumlah_curas']]; return ['index' => $index + 1, 'jumlah_curas' => $item['jumlah_curas']];
})->sortBy('jumlah_curas')->values(); })->sortBy('jumlah_curas')->values();
$centroidToKlaster = []; $centroidToKlaster = [];
foreach ($finalCentroids as $i => $centroid) { foreach ($finalCentroids as $i => $centroid) {
// Klaster ID mulai dari 1 (asumsi klaster di DB bernomor 1, 2, 3, ...)
$centroidToKlaster[$centroid['index']] = $i + 1; $centroidToKlaster[$centroid['index']] = $i + 1;
} }
// Update ke database // Update ke database
foreach ($data as $item) { foreach ($data as $item) {
Curas::where('id', $item->id)->update([ Curas::where('id', $item->id)->update([
@ -101,19 +91,23 @@ public function hitungKMeansCuras()
]); ]);
} }
// Format centroid awal
$centroidAwalFormatted = collect($centroidAwal)->values()->map(function ($item, $index) { $centroidAwalFormatted = collect($centroidAwal)->values()->map(function ($item, $index) {
return ['C' . ($index + 1) => $item['jumlah_curas']]; return ['C' . ($index + 1) => $item['jumlah_curas']];
}); });
// Format centroid akhir
$centroidAkhirFormatted = $centroids->values()->map(function ($item, $index) {
return ['C' . ($index + 1) => $item['jumlah_curas']];
});
return [ return [
'centroid_awal' => $centroidAwalFormatted, 'centroid_awal' => $centroidAwalFormatted,
'centroid_akhir' => $centroidAkhirFormatted,
'iterasi' => $iterasi 'iterasi' => $iterasi
]; ];
} }
public function hitungKMeansCuranmor() public function hitungKMeansCuranmor()
{ {
$data = Curanmor::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curanmor')->orderBy('jumlah_curanmor', 'asc')->get(); $data = Curanmor::select('id', 'kecamatan_id', 'klaster_id', 'jumlah_curanmor')->orderBy('jumlah_curanmor', 'asc')->get();
@ -121,23 +115,20 @@ public function hitungKMeansCuranmor()
$k = Klaster::count('id'); $k = Klaster::count('id');
$maxIterasi = 100; $maxIterasi = 100;
$uniqueCount = $data->unique('jumlah_curanmor')->count(); $minValue = $data->min('jumlah_curanmor');
if ($uniqueCount < $k) { $maxValue = $data->max('jumlah_curanmor');
throw new \Exception("Jumlah nilai unik pada 'jumlah_curanmor' ($uniqueCount) kurang dari jumlah klaster ($k). Pastikan data memiliki variasi yang cukup.");
$generated = collect();
while ($generated->count() < $k) {
$randomFloat = round(mt_rand($minValue * 100, $maxValue * 100) / 100, 2);
if (!$generated->contains($randomFloat)) {
$generated->push($randomFloat);
}
} }
// Ambil centroid awal yang unik $centroids = $generated->map(function ($value) {
$centroids = $data->unique('jumlah_curanmor') // Pastikan nilai unik return ['jumlah_curanmor' => $value];
->shuffle() // Acak
->take($k) // Ambil k data
->values()
->map(function ($item) {
return [
'jumlah_curanmor' => $item->jumlah_curanmor,
];
}); });
// Simpan centroid awal sebelum iterasi
$centroidAwal = $centroids->toArray(); $centroidAwal = $centroids->toArray();
$iterasi = []; $iterasi = [];
@ -225,15 +216,17 @@ public function SSEElbowCuras()
$maxIterasi = 100; $maxIterasi = 100;
$elbowData = []; $elbowData = [];
for ($k = 1; $k <= $maxK; $k++) { // Ambil nilai minimum dan maksimum dari jumlah_curas
// Inisialisasi centroid awal secara acak $min = $data->min('jumlah_curas');
$centroids = $data->unique('jumlah_curas')->shuffle()->take($k)->values()->map(function ($item) { $max = $data->max('jumlah_curas');
return ['jumlah_curas' => $item->jumlah_curas];
for ($k = 2; $k <= $maxK; $k++) {
// Inisialisasi centroid awal sebagai float acak dalam rentang min-max
$centroids = collect(range(1, $k))->map(function () use ($min, $max) {
return ['jumlah_curas' => mt_rand($min * 100, $max * 100) / 100];
}); });
// Simpan centroid awal sebagai array angka
$centroidAwal = $centroids->pluck('jumlah_curas')->toArray(); $centroidAwal = $centroids->pluck('jumlah_curas')->toArray();
$prevAssignment = []; $prevAssignment = [];
for ($iter = 0; $iter < $maxIterasi; $iter++) { for ($iter = 0; $iter < $maxIterasi; $iter++) {
@ -259,7 +252,6 @@ public function SSEElbowCuras()
$prevAssignment = $currentAssignment; $prevAssignment = $currentAssignment;
// Update centroid
foreach ($clustered as $key => $group) { foreach ($clustered as $key => $group) {
$avg = collect($group)->avg('jumlah_curas'); $avg = collect($group)->avg('jumlah_curas');
$centroids = $centroids->map(function ($centroid, $idx) use ($key, $avg) { $centroids = $centroids->map(function ($centroid, $idx) use ($key, $avg) {
@ -270,7 +262,7 @@ public function SSEElbowCuras()
} }
} }
// Hitung SSE untuk k saat ini // Hitung SSE
$sse = 0; $sse = 0;
foreach ($clustered as $key => $group) { foreach ($clustered as $key => $group) {
$centroidVal = $centroids[$key]['jumlah_curas']; $centroidVal = $centroids[$key]['jumlah_curas'];
@ -286,7 +278,8 @@ public function SSEElbowCuras()
]; ];
} }
// Simpan ke file
file_put_contents( file_put_contents(
storage_path('app/public/sse_elbow_curas.json'), storage_path('app/public/sse_elbow_curas.json'),
json_encode($elbowData, JSON_PRETTY_PRINT) json_encode($elbowData, JSON_PRETTY_PRINT)
@ -294,6 +287,7 @@ public function SSEElbowCuras()
} }
public function SSEElbowCuranmor() public function SSEElbowCuranmor()
{ {
$data = Curanmor::select('id', 'jumlah_curanmor')->get(); $data = Curanmor::select('id', 'jumlah_curanmor')->get();
@ -301,7 +295,7 @@ public function SSEElbowCuranmor()
$maxIterasi = 100; $maxIterasi = 100;
$elbowData = []; $elbowData = [];
for ($k = 1; $k <= $maxK; $k++) { for ($k = 2; $k <= $maxK; $k++) {
// Inisialisasi centroid awal secara acak // Inisialisasi centroid awal secara acak
$centroids = $data->unique('jumlah_curanmor')->shuffle()->take($k)->values()->map(function ($item) { $centroids = $data->unique('jumlah_curanmor')->shuffle()->take($k)->values()->map(function ($item) {
return ['jumlah_curanmor' => $item->jumlah_curanmor]; return ['jumlah_curanmor' => $item->jumlah_curanmor];