Dataset 270 baris hasil Bagian 2 sudah layak dianalisis: setiap kota utuh 90 hari, tanpa NaN, dan seluruh nilai dalam batas fisik. Pertanyaannya berubah dari “apakah datanya benar” menjadi “apa polanya”. Empat pertanyaan pola paling umum kita kerjakan berurutan: bagaimana kecenderungan suhu tujuh hari terakhir, bagaimana rekap bulanan tiap kota, berapa banyak hari hujan, dan kapan cuaca ekstrem terjadi. Skrip pola_cuaca.py memuat data lewat fungsi muat_bersih() dari Bagian 2, lalu menjawab keempatnya dalam satu keluaran.
"""Menganalisis pola cuaca: rata-rata bergerak, agregasi bulanan, hari hujan, ekstrem."""
from pathlib import Path
import tempfile
import pandas as pd
from buat_cuaca import buat_cuaca, muat_bersih
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as folder:
berkas = Path(folder) / "cuaca.csv"
buat_cuaca().to_csv(berkas, index=False)
df0 = muat_bersih(pd.read_csv(berkas))
df = df0.set_index("tanggal").sort_index()
# 1. rata-rata bergerak 7 hari per kota
ma = df.groupby("kota")["suhu_max"].rolling(7).mean()
print("== Rata-rata bergerak 7 hari suhu_max Bandung ==")
print(ma.loc["Bandung"].head(10).round(2).to_string())
# 2. agregasi bulanan per kota
bulanan = df.groupby("kota").resample("ME").agg(
suhu_rata=("suhu_max", "mean"),
hujan_total=("curah_hujan", "sum"),
).round(1)
print("\n== Agregasi bulanan per kota (resample ME) ==")
print(bulanan.to_string())
# 3. hari hujan dengan boolean mask
df["hujan"] = df["curah_hujan"] >= 1.0
hari_hujan = df.groupby("kota").resample("ME")["hujan"].sum().unstack("kota")
print("\n== Hari hujan (>= 1 mm) per bulan ==")
print(hari_hujan.astype("int64").to_string())
# 4. hari ekstrem dengan idxmax dan idxmin
idx_panas = df0.groupby("kota")["suhu_max"].idxmax()
idx_dingin = df0.groupby("kota")["suhu_min"].idxmin()
print("\n== Hari terpanas per kota ==")
print(df0.loc[idx_panas, ["kota", "tanggal", "suhu_max"]].to_string(index=False))
print("\n== Hari terdingin per kota ==")
print(df0.loc[idx_dingin, ["kota", "tanggal", "suhu_min"]].to_string(index=False))Output yang Dihasilkan:
== Rata-rata bergerak 7 hari suhu_max Bandung ==
tanggal
2026-01-01 NaN
2026-01-02 NaN
2026-01-03 NaN
2026-01-04 NaN
2026-01-05 NaN
2026-01-06 NaN
2026-01-07 27.54
2026-01-08 27.20
2026-01-09 27.14
2026-01-10 26.99
== Agregasi bulanan per kota (resample ME) ==
suhu_rata hujan_total
kota tanggal
Bandung 2026-01-31 27.1 224.3
2026-02-28 27.4 175.1
2026-03-31 27.2 146.1
Medan 2026-01-31 32.5 221.4
2026-02-28 32.4 132.5
2026-03-31 32.2 97.6
Yogyakarta 2026-01-31 31.4 154.6
2026-02-28 31.5 194.8
2026-03-31 31.7 158.3
== Hari hujan (>= 1 mm) per bulan ==
kota Bandung Medan Yogyakarta
tanggal
2026-01-31 19 18 12
2026-02-28 13 12 19
2026-03-31 19 13 15
== Hari terpanas per kota ==
kota tanggal suhu_max
Bandung 2026-03-08 30.5
Medan 2026-01-02 34.8
Yogyakarta 2026-01-07 35.2
== Hari terdingin per kota ==
kota tanggal suhu_min
Bandung 2026-01-02 15.3
Medan 2026-01-08 21.7
Yogyakarta 2026-02-05 21.0Rata-rata Bergerak Tujuh Hari #
Bagian pertama skrip menghitung df.groupby("kota")["suhu_max"].rolling(7).mean(): rata-rata suhu maksimum dari tujuh hari terakhir, dihitung per kota. Enam baris pertama Bandung bernilai NaN karena jendela tujuh hari belum penuh — min_periods bawaan rolling() sama dengan ukuran jendelanya, sehingga nilai pertama baru muncul pada 7 Januari (27.54). Angka mulus seperti ini berguna untuk melicin fluktuasi harian: lonjakan satu hari hilang, kecenderungan mingguan tampak. Perlu digarisbawahi bahwa rolling() di dalam groupby() menghitung per kelompok, sehingga jendela Bandung tidak pernah mencampur data Medan.
Rekap Bulanan dengan resample ME #
Pertanyaan kedua dijawab dengan df.groupby("kota").resample("ME").agg(...): index tanggal dibagi per akhir bulan, lalu named aggregation menghitung rata-rata suhu_max dan total curah_hujan tiap kelompok. Alias wajib ME (month end) dipakai karena alias lama "M" sudah dihapus pada pandas 3.0 — materi lengkapnya ada pada chapter Resampling Data. Tabel sembilan baris hasilnya memperlihatkan tiga watak berbeda: Bandung mengering bertahap (224.3 menjadi 175.1 lalu 146.1 mm), Medan kering lebih tajam (221.4 menjadi 97.6 mm), dan Yogyakarta justru berpuncak pada Februari (194.8 mm) sebelum turun lagi di Maret. Suhu rata-rata tiap kota nyaris stabil antarbulan, sehingga cerita utama data ini ada di curah hujan.
Menghitung Hari Hujan dengan Boolean Mask #
Total curah hujan bisa tinggi karena satu hari badai saja, sedangkan banyaknya hari hujan menceritakan hal lain: frekuensi. Konvensi umum meteorologi menyebut hari hujan bila curah hujan minimal 1 mm, dan konvensi itu diterjemahkan langsung menjadi boolean mask: df["hujan"] = df["curah_hujan"] >= 1.0 menghasilkan kolom True/False per baris. Menjumlahkan boolean dengan sum() menghitung banyaknya True, dan karena perhitungannya per bulan, kita memakai groupby("kota").resample("ME")["hujan"].sum() seperti bagian sebelumnya. Method unstack("kota") memutar level kota dari index menjadi kolom sehingga hasilnya tabel ringkas 3 baris x 3 kolom: Bandung 19-13-19 hari, Medan 18-12-13 hari, dan Yogyakarta 12-19-15 hari per bulan Januari-Maret. Yogyakarta lagi-lagi beda arah — paling sedikit hujan pada Januari, paling banyak pada Februari.
Hari Ekstrem dengan idxmax dan idxmin #
Pertanyaan terakhir mencari tanggal tepat ketika rekor terjadi. df0.groupby("kota")["suhu_max"].idxmax() mengembalikan label baris tempat suhu_max tertinggi untuk setiap kota, dan idxmin() melakukan hal sebaliknya untuk suhu_min. Pemilihan barisnya dilakukan di df0 — DataFrame dengan index bilangan bulat bawaan — bukan pada frame berindex tanggal. Alasannya teknis: index tanggal berisi nilai duplikat antarkota, sehingga df.loc["2026-01-02"] akan mengembalikan baris ketiga-tiga kota sekaligus, bukan baris yang dimaksud idxmax().
Hasilnya informatif. Hari terpanas Medan (34.8 derajat, 2 Januari) dan Yogyakarta (35.2 derajat, 7 Januari) terjadi di awal periode, sementara Bandung yang sejuk bahkan mencapai rekor panasnya di akhir periode (30.5 derajat, 8 Maret). Hari terdingin Bandung (15.3 derajat, 2 Januari) jauh di bawah dua kota lain yang bergantian di kisaran 21-22 derajat.
Empat tabel pada keluaran skrip ini masih berbentuk data kerja: index bertipe datetime, angka penuh desimal, dan belum ada berkas keluaran. Bagian 4 merapikannya menjadi ringkasan siap lapor — agregasi mingguan, matriks kota x bulan, statistik ringkas, dan ekspor CSV.
Sumber bacaan resmi: pandas.DataFrame.resample , pandas.DataFrame.rolling , dan pandas.DataFrame.idxmax .