Dinas Pertanian Kota Sukamaju meminta kita menganalisis rekaman cuaca tahun 2025: satu baris per hari berisi suhu rata-rata, suhu minimum, suhu maksimum, dan curah hujan. Data asli milik kantor tidak boleh kami umumkan, maka kita membangun dataset sintetis dengan pola yang mirip aslinya — gelombang musiman, tren pelan, dan derau harian. Kuncinya adalah generator ber-seed: dengan np.random.default_rng(42), angka yang “acak” itu tetap identik di komputer siapa pun, sebagaimana dibahas pada Chapter default_rng dan Generator dan Reproducibility dan Random Seed.
Bagian ini menyiapkan fondasi seluruh proyek. Kita membangun array 365 × 4, menulisnya ke berkas CSV di folder data/, membacanya kembali dengan np.loadtxt, lalu memeriksa struktur awalnya sebelum dipakai menghitung apa pun. Pada proyek sungguhan, langkah-langkah ini kita simpan sebagai src/persiapan.py agar keempat bagian proyek memakai definisi data yang sama.
Membangun Dataset Cuaca Sintetis #
Rumus datanya sederhana namun menyerupai cuaca tropis. Suhu rata-rata mengikuti gelombang kosinus setahun penuh dengan amplitudo 2.5 °C, ditambah tren pemanasan kecil 0.001 °C per hari dan derau harian. Suhu minimum dan maksimum mengikuti suhu rata-rata dengan jarak tetap, sedangkan curah hujan berupa nilai acak yang dipotong pada nol lalu diperbesar saat musim hujan di awal dan akhir tahun:
import numpy as np
# Pembangun data cuaca Kota Sukamaju, tahun 2025 — dipakai di seluruh proyek.
rng = np.random.default_rng(42)
t = np.arange(365)
noise = rng.normal(0.0, 1.0, (365, 3)) * np.array([0.8, 0.6, 0.8])
musim = -np.cos(2 * np.pi * t / 365)
suhu_rata = 27.0 + 2.5 * musim + 0.001 * t + noise[:, 0]
suhu_min = suhu_rata - 3.5 + noise[:, 1]
suhu_max = suhu_rata + 4.0 + noise[:, 2]
curah_hujan = np.clip(rng.normal(5.0, 7.0, 365), 0.0, None)
curah_hujan *= 1 + 0.7 * np.cos(2 * np.pi * t / 365)
data = np.column_stack([suhu_rata, suhu_min, suhu_max, curah_hujan])
print(data.shape, data.dtype)
print(np.round(data[:3], 2))Output yang Dihasilkan:
(365, 4) float64
[[24.74 20.62 29.34 23.09]
[25.25 20.58 28.21 27.29]
[24.61 20.92 28.59 14.38]]Mari kita bedah pembangun data ini:
- Baris 4 (
rng = np.random.default_rng(42)): generator acak PCG64 dibuat dengan seed 42; seluruh derau proyek keluar dari objek ini sehingga hasilnya dapat direproduksi. - Baris 6 (
noise = ...): satu panggilannormalmenghasilkan matriks 365 × 3 berisi derau standar, lalu dikalikan vektor simpangan baku per kolom (0.8,0.6,0.8) sehingga tiap besaran punya keragaman yang berbeda. - Baris 7 (
musim = ...):-np.cos(2πt/365)bernilai-1pada 1 Januari dan+1di pertengahan tahun; bentuk ini dipilih agar gelombang musiman tidak tertukar dengan tren linear saat regresi di Bagian 4. - Baris 8-10: suhu rata-rata disusun dari dasar 27.0 °C, gelombang musiman, tren, dan derau; suhu minimum serta maksimum digeser -3.5 °C dan +4.0 °C dari suhu rata-rata ditambah derau sendiri.
- Baris 11-12: derau hujan yang bernilai negatif dipangkas ke
0.0olehnp.clip(lihat Chapter Clamp dan clip), lalu dikalikan faktor1 + 0.7·cosyang membuat awal dan akhir tahun lebih basah. - Baris 13 (
np.column_stack): empat array 1-D sepanjang 365 disusun berdampingan menjadi matriks 365 × 4, satu kolom per besaran.
Menulis dan Membaca Ulang CSV #
Bentuk paling portabel untuk pertukaran data tabular adalah CSV. Kita menulis matriks dengan np.savetxt lengkap dengan baris header, membacanya kembali dengan np.loadtxt (dibahas tuntas pada Chapter Membaca dan Menulis CSV), lalu membandingkan hasilnya dengan data di memori:
import numpy as np
# Pembangun data (identik dengan blok sebelumnya).
rng = np.random.default_rng(42)
t = np.arange(365)
noise = rng.normal(0.0, 1.0, (365, 3)) * np.array([0.8, 0.6, 0.8])
musim = -np.cos(2 * np.pi * t / 365)
suhu_rata = 27.0 + 2.5 * musim + 0.001 * t + noise[:, 0]
suhu_min = suhu_rata - 3.5 + noise[:, 1]
suhu_max = suhu_rata + 4.0 + noise[:, 2]
curah_hujan = np.clip(rng.normal(5.0, 7.0, 365), 0.0, None)
curah_hujan *= 1 + 0.7 * np.cos(2 * np.pi * t / 365)
data = np.column_stack([suhu_rata, suhu_min, suhu_max, curah_hujan])
np.savetxt("cuaca_sukamaju.csv", data, delimiter=",", fmt="%.2f",
header="suhu_rata,suhu_min,suhu_max,curah_hujan", comments="")
data_kembali = np.loadtxt("cuaca_sukamaju.csv", delimiter=",", skiprows=1)
print("identik persis:", np.array_equal(data, data_kembali))
print("allclose atol=0.01:", np.allclose(data, data_kembali, atol=0.01))
with open("cuaca_sukamaju.csv") as f:
for i, baris in enumerate(f):
if i >= 2:
break
print(baris.rstrip())Output yang Dihasilkan:
identik persis: False
allclose atol=0.01: True
suhu_rata,suhu_min,suhu_max,curah_hujan
24.74,20.62,29.34,23.09Yang perlu kita cermati:
savetxtdenganheader=...dancomments="": baris pertama berkas berisi nama kolom tanpa tanda#;fmt="%.2f"membulatkan setiap nilai menjadi dua desimal agar berkas mudah dibaca manusia.loadtxtdenganskiprows=1: baris header dilewati sehingga yang terbaca adalah data numerik murni, kembali sebagai arrayfloat64berbentuk(365, 4).array_equalmenghasilkanFalse: format%.2fmembulatkan nilai, misalnya24.7439menjadi24.74; selisihnya di bawah satu persen satuan, itulah sebabnyanp.allclosedenganatol=0.01mengembalikanTrue.
Pemeriksaan Struktur Awal #
Sebelum data dipakai, kita lakukan pemeriksaan kesehatan singkat: bentuk dan dtype sesuai ekspektasi, tidak ada nan yang lolos, dan semua nilai berada dalam rentang yang wajar untuk data cuaca:
import numpy as np
# Pembangun data (identik dengan blok sebelumnya).
rng = np.random.default_rng(42)
t = np.arange(365)
noise = rng.normal(0.0, 1.0, (365, 3)) * np.array([0.8, 0.6, 0.8])
musim = -np.cos(2 * np.pi * t / 365)
suhu_rata = 27.0 + 2.5 * musim + 0.001 * t + noise[:, 0]
suhu_min = suhu_rata - 3.5 + noise[:, 1]
suhu_max = suhu_rata + 4.0 + noise[:, 2]
curah_hujan = np.clip(rng.normal(5.0, 7.0, 365), 0.0, None)
curah_hujan *= 1 + 0.7 * np.cos(2 * np.pi * t / 365)
data = np.column_stack([suhu_rata, suhu_min, suhu_max, curah_hujan])
print("shape :", data.shape)
print("dtype :", data.dtype)
print("sel nan:", np.isnan(data).sum())
bawah = np.array([10.0, 10.0, 15.0, 0.0])
atas = np.array([45.0, 45.0, 50.0, 250.0])
print("dalam rentang wajar:", bool(np.all((data >= bawah) & (data <= atas))))
print("min per kolom:", np.round(data.min(axis=0), 2))
print("max per kolom:", np.round(data.max(axis=0), 2))Output yang Dihasilkan:
shape : (365, 4)
dtype : float64
sel nan: 0
dalam rentang wajar: True
min per kolom: [23.29 19.32 26.71 0. ]
max per kolom: [31.24 28.75 35.84 35.83]Tiga hal dari pemeriksaan ini:
data.shapedandata.dtype: 365 baris untuk 365 hari tahun 2025 dan 4 kolom untuk empat besaran; dtypefloat64adalah pilihan wajar untuk pengukuran fisik (lihat Chapter Atribut shape dan Atribut dtype).np.isnan(data).sum()bernilai 0: data sintetis kita memang bersih; pada Bagian 2 kita akan menyisipkan kegagalan sensor secara sengaja agar latihan pembersihan terasa nyata.- Perbandingan
data >= bawahdandata <= atas: broadcasting membandingkan setiap sel dengan batas kolomnya, operator&menggabungkan kedua mask, dannp.allmemastikan tidak ada satu pun pelanggaran.
Ringkasan kolom dataset yang akan kita pakai sepanjang proyek:
| Kolom | Arti | Satuan | Rentang teramati |
|---|---|---|---|
suhu_rata | suhu udara harian rata-rata | °C | 23.29 - 31.24 |
suhu_min | suhu minimum harian | °C | 19.32 - 28.75 |
suhu_max | suhu maksimum harian | °C | 26.71 - 35.84 |
curah_hujan | curah hujan harian | mm | 0.00 - 35.83 |
Data sudah siap di folder data/ dan strukturnya sehat. Bagian berikutnya, Bagian 2: Membersihkan Data, sengaja merusak sebagian pembacaan sensor agar kita berlatih mendeteksi dan memperbaikinya.