Bagian terakhir proyek ini menjawab pertanyaan yang biasanya diajukan pengambil kebijakan: apakah suhu Kota Sukamaju bergerak naik, hari-hari mana yang menyimpang jauh dari normal, dan bagaimana keseluruhan temuan tahun 2025 dirangkum untuk laporan. Kita tetap bekerja pada data bersih yang sama — satu pembangun dataset, satu seed, sehingga setiap angka di laporan ini dapat diulang pembaca.
Tiga alat baru dipakai di sini, dan semuanya telah dibahas di section sebelumnya: regresi linear lewat np.linalg.lstsq dari Chapter Least Squares, penyaringan anomali dengan boolean indexing dari Chapter Boolean Indexing, serta pemeriksaan otomatis dari Chapter numpy.testing. Hasil akhirnya adalah laporan ringkas bertabel yang menggabungkan temuan Bagian 1 sampai 4.
Mengukur Tren Suhu dengan Least Squares #
Gelombang musiman membuat tren sulit dibaca mata: suhu naik hingga pertengahan tahun lalu turun, padahal tren sejati bertambah pelan setiap hari. Regresi linear memisahkan keduanya. Kita menyusun matriks perancang A berisi kolom t dan kolom konstanta, lalu membiarkan lstsq mencari kemiringan dan intersep yang paling mendekati data:
import numpy as np
# Pembangun data cuaca 2025 (data bersih, tanpa sentinel).
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])
A = np.vstack([t, np.ones(365)]).T
koef = np.linalg.lstsq(A, suhu_rata, rcond=None)[0]
slope, intersep = koef
print(f"slope : {slope:.6f} °C/hari")
print(f"intersep : {intersep:.2f} °C")
print(f"tren : {slope * 3650:+.2f} °C per dekade")
print("selisih rata-rata 30 hari akhir vs awal:",
f"{suhu_rata[-30:].mean() - suhu_rata[:30].mean():+.2f} °C")Output yang Dihasilkan:
slope : 0.001362 °C/hari
intersep : 26.94 °C
tren : +4.97 °C per dekade
selisih rata-rata 30 hari akhir vs awal: +0.47 °CMari kita bedah:
np.vstack([t, np.ones(365)]).T: dua kolom disusun lalu ditransposkan menjadi matriks 365 × 2; kolom pertama adalah hari, kolom kedua konstanta satu untuk intersep.np.linalg.lstsq(A, suhu_rata, rcond=None)[0]: menyelesaikan persamaanA · koef ≈ suhu_ratadalam arti kuadrat terkecil; elemen[0]adalah vektor koefisien(slope, intersep).slope * 3650: mengonversi kemiringan per hari menjadi per dekade — bahasa yang lebih bermakna bagi pembaca laporan.slope ≈ 0.001362: dataset memang ditanami tren 0.001 °C per hari (≈ +3.7 °C per dekade); estimasi sedikit meleset karena 365 sampel dengan derau 0.8 °C, dan selisih rata-rata 30 hari akhir versus awal (+0.47 °C) memberi dukungan kasar pada arah yang sama.
Angka +4.97 °C per dekade jauh di atas tren iklim nyata — wajar, karena tren pada data simulasi sengaja diperbesar agar polanya terlihat dalam satu tahun saja. Yang diuji di sini adalah metodenya: dengan data panjang dan derau yang sama, formula yang identik itulah yang dipakai pada data sungguhan.
Menyaring Anomali Lebih dari 2 Std #
Anomali adalah selisih suhu harian terhadap rata-rata tahunan. Hari yang anomali lebih dari dua simpangan baku jarang terjadi secara kebetulan — untuk derau normal, sekitar 4.6% hari — sehingga layak ditandai. Kita memakai Chapter Variance dan Standard Deviation untuk ambangnya dan boolean indexing untuk menyaringnya:
import numpy as np
# Pembangun data cuaca 2025 (data bersih, tanpa sentinel).
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])
anomali = suhu_rata - suhu_rata.mean()
ambang = 2 * suhu_rata.std()
tanggal = np.arange(np.datetime64("2025-01-01"), np.datetime64("2026-01-01"))
ekstrem = np.abs(anomali) > ambang
print("ambang :", f"{ambang:.2f} °C")
print("jumlah hari anomali > 2 std:", np.count_nonzero(ekstrem))
for i in np.where(ekstrem)[0]:
arah = "di atas" if anomali[i] > 0 else "di bawah"
print(f"{tanggal[i]} {arah} rata-rata {abs(anomali[i]):.2f} °C")Output yang Dihasilkan:
ambang : 3.82 °C
jumlah hari anomali > 2 std: 3
2025-01-21 di bawah rata-rata 3.87 °C
2025-07-15 di atas rata-rata 4.05 °C
2025-12-09 di bawah rata-rata 3.90 °CCatatan pembacaan:
anomali = suhu_rata - suhu_rata.mean(): pengurangan array dengan skalar menggeser seluruh deret sehingga rata-ratanya nol.np.abs(anomali) > ambang: mask boolean yang menyala untuk hari yang terlalu panas atau terlalu dingin;np.count_nonzeromenjumlahkan hari-hari tersebut.np.where(ekstrem)[0]: mengubah mask menjadi indeks agar tanggalnya bisa dicetak satu per satu.- Hasilnya hanya 3 hari, dan dua di antaranya bertepatan dengan rekaman Bagian 3 — hari terpanas 15 Juli dan hari terdingin 9 Desember. Anomali menangkap persis hari-hari yang layak diperiksa ulang oleh pemantau cuaca.
Verifikasi Proyek #
Proyek data belum selesai sebelum perhitungannya teruji. Blok berikut meniru isi tests/uji_statistik.py: membangun ulang data dari seed yang sama, lalu memastikan invarian-invarian kunci dengan assert dan numpy.testing sebagaimana dipraktikkan pada Chapter assert_allclose dan Reproducible Numerical Tests:
import numpy as np
# Pembangun data cuaca 2025 — sumber tunggal 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])
assert data.shape == (365, 4)
assert data.dtype == np.float64
assert np.all(suhu_max > suhu_min)
assert np.all(curah_hujan >= 0.0)
assert np.isnan(data).sum() == 0
rata_bulanan = suhu_rata[:360].reshape(12, 30).mean(axis=1)
np.testing.assert_allclose(rata_bulanan.mean(), suhu_rata[:360].mean())
prediksi = 27.0 + 2.5 * musim + 0.001 * t
print("residual maksimum:", round(np.abs(suhu_rata - prediksi).max(), 2))
print("Semua pemeriksaan lulus")Output yang Dihasilkan:
residual maksimum: 2.54
Semua pemeriksaan lulusSetiap baris pemeriksaan menjaga satu janji:
- Shape dan dtype: dataset tetap 365 × 4
float64— fondasi bagi semua perhitungan lain. suhu_max > suhu_minuntuk semua hari: hukum fisika sederhana yang seharusnya tak pernah dilanggar data.curah_hujan >= 0dan tanpanan: hasil pemangkasan dan pembersihan tetap berlaku.assert_allcloserata-rata bulanan: rata-rata dari 12 rata-rata blok sama dengan rata-rata 360 hari langsung — bukti bahwa pembagian blok tidak menggeser angka.residual maksimum2.54: selisih terbesar data terhadap rumus pembangunnya sepenuhnya berasal dari derau; tidak ada langkah analisis yang mengubah data.
Laporan #
Seluruh temuan proyek merangkum dalam dua tabel berikut. Angka-angka bulanan memakai pembagian 12 × 30 hari sesuai kesepakatan Bagian 3:
| Bulan | Suhu rata-rata (°C) | Curah hujan (mm) |
|---|---|---|
| Januari | 24.63 | 360.8 |
| Februari | 25.22 | 280.2 |
| Maret | 26.31 | 157.2 |
| April | 27.83 | 153.2 |
| Mei | 28.74 | 95.6 |
| Juni | 29.50 | 63.3 |
| Juli | 29.59 | 51.2 |
| Agustus | 28.88 | 76.8 |
| September | 28.07 | 155.2 |
| Oktober | 26.77 | 208.0 |
| November | 25.86 | 346.1 |
| Desember | 25.26 | 295.4 |
Tabel bulanan hanya mencakup 360 hari pertama; total hujan tahunan pada tabel ringkasan di bawah dihitung dari 365 hari penuh.
| Indikator | Nilai |
|---|---|
| Rata-rata suhu tahunan | 27.19 °C |
| Bulan terpanas | Juli (29.59 °C) |
| Bulan terdingin | Januari (24.63 °C) |
| Hari terpanas | 15 Juli (31.24 °C) |
| Hari terdingin | 9 Desember (23.29 °C) |
| Total curah hujan setahun | 2338.0 mm |
| Hari hujan (> 1 mm) | 257 (70.4%) |
| Hujan harian terbesar | 28 Desember (35.8 mm) |
| Tren suhu (estimasi lstsq) | +0.001362 °C/hari ≈ +4.97 °C/dekade |
| Hari dengan anomali > 2 std | 3 |
Tiga kesimpulan layak ditulis di halaman pertama laporan. Pertama, suhu Kota Sukamaju mengikuti gelombang musiman dengan rentang hampir 5 °C antara Januari dan Juli, plus tren pemanasan yang terukur meski datanya satu tahun. Kedua, hujan bergerak berlawanan dengan suhu: Januari terbasah (360.8 mm) justru saat suhu terendah, Juli terkering (51.2 mm) saat suhu tertinggi — pola kemarau pertengahan tahun yang khas. Ketiga, hanya tiga hari yang menyimpang lebih dari 2 std dan semuanya sudah teridentifikasi dengan tanggalnya, sehingga pemantauan berikutnya bisa berfokus pada hari serupa.
Sebagai proyek utuh, struktur foldernya kini lengkap: data/cuaca_sukamaju.csv sebagai bahan baku, src/persiapan.py dan src/pola.py sebagai kode analisis, tests/uji_statistik.py dari blok verifikasi di atas, serta laporan/laporan-cuaca.md berisi dua tabel di bagian ini.