Rapat akhir tahun tidak puas dengan satu angka penutup; yang diminta adalah ringkasan yang bisa diperbandingkan antar komoditas: level tipikal, hari ekstrem, fluktuasi, dan arah tren. Bagian 3: Menganalisis Statistik mengubah array harga dari Bagian 1: Menyiapkan Data Harga beserta pct dari Bagian 2: Menghitung Perubahan Harga menjadi statistik semacam itu — seluruhnya lewat agregasi axis=0 (Chapter Operasi Berdasarkan Axis) yang meringkas 360 hari tiap kolom menjadi satu angka per komoditas, tanpa satu pun loop eksplisit.
Dalam struktur proyek, seluruh perhitungan bagian ini kita kemas sebagai src/statistik.py.
Rata-rata dan Median per Komoditas #
Rata-rata memberi level tipikal setahun; median memberi level hari yang biasa (Chapter mean, median, dan percentile) — dan keduanya berbeda kepekaan terhadap nilai ekstrem:
import numpy as np
# Pembangun deret harga 2025 (identik dengan Bagian 1).
rng = np.random.default_rng(42)
awal = np.array([15000.0, 45000.0, 18000.0])
drift = np.array([0.0003, 0.0012, 0.0004])
sigma = np.array([0.006, 0.030, 0.010])
musim = 1 - np.array([0.01, 0.15, 0.03]) * np.cos(2 * np.pi * np.arange(360) / 360)[:, None]
guncangan = drift + rng.normal(0.0, sigma, (360, 3))
harga = awal * np.cumprod(1 + guncangan, axis=0) * musim
print("rata-rata :", np.round(harga.mean(axis=0), 2))
print("median :", np.round(np.median(harga, axis=0), 2))Output yang Dihasilkan:
rata-rata : [15615.85 39532.07 18347.36]
median : [15653.81 38487.91 18072.53]Cara membacanya:
harga.mean(axis=0)/np.median(harga, axis=0): keduanya mengagregasi sepanjang axis hari menjadi satu nilai per komoditas; median mengabaikan ujung-ujung sebaran.- Selisih keduanya adalah pemeriksa kemiringan sebaran yang murah: mean cabai 1 044 lebih tinggi dari median (+2.7%) — lonjakan musim menyeret rata-rata ke atas, sehingga median 38 487.91 lebih mewakili “hari biasa” cabai; minyak goreng menyusul (+1.5%); pada beras keduanya hampir bertemu (−0.2%).
Hari Terendah dan Tertinggi #
Bagian 1: Menyiapkan Data Harga sudah mencetak min/max per komoditas, tetapi tidak menjawab pertanyaan yang pasti muncul: kapan? argmin/argmax (Chapter min, max, argmin, dan argmax) memberi posisi hari, dan kolom tanggal pendamping mengubahnya menjadi tanggal:
import numpy as np
# Pembangun deret harga 2025 (identik dengan Bagian 1).
rng = np.random.default_rng(42)
awal = np.array([15000.0, 45000.0, 18000.0])
drift = np.array([0.0003, 0.0012, 0.0004])
sigma = np.array([0.006, 0.030, 0.010])
musim = 1 - np.array([0.01, 0.15, 0.03]) * np.cos(2 * np.pi * np.arange(360) / 360)[:, None]
guncangan = drift + rng.normal(0.0, sigma, (360, 3))
harga = awal * np.cumprod(1 + guncangan, axis=0) * musim
tanggal = np.arange(np.datetime64("2025-01-01"), np.datetime64("2025-12-27"))
nama = np.array(["beras", "cabai", "minyak goreng"])
i_min = np.argmin(harga, axis=0)
i_max = np.argmax(harga, axis=0)
print("indeks min:", i_min, "| indeks max:", i_max)
for k in range(3):
print(f"{nama[k]:<14} min {tanggal[i_min[k]]} {harga[i_min[k], k]:9.2f}")
print(f"{nama[k]:<14} max {tanggal[i_max[k]]} {harga[i_max[k], k]:9.2f}")Output yang Dihasilkan:
indeks min: [ 83 316 348] | indeks max: [359 112 183]
beras min 2025-03-25 14764.39
beras max 2025-12-26 16825.21
cabai min 2025-11-13 30267.24
cabai max 2025-04-23 52830.47
minyak goreng min 2025-12-15 16042.13
minyak goreng max 2025-07-03 21901.49Rincian mekanismenya:
np.argmin(harga, axis=0): indeks baris harga terendah tiap kolom —[ 83 316 348];argmaxsama untuk tertinggi.harga[i_min[k], k]dantanggal[i_min[k]]: nilai dan tanggal diambil pada indeks yang sama —datetime64diindeks langsung dengan posisi hari (pola Chapter datetime64 dan timedelta64) — sehingga keduanya dijamin sinkron.- Bacaan: beras mencetak tertingginya justru pada hari terakhir (26 Desember, 16 825.21 — persis harga penutupan Bagian 1: Menyiapkan Data Harga); tren naiknya belum berbalik. Cabai memuncak 23 April lalu mengakhiri tahun di dasarnya 13 November — tanggal yang sama dengan penurunan harian terbesar di Bagian 2: Menghitung Perubahan Harga (−3 744.06).
Volatilitas dan Korelasi #
Volatilitas dihitung ulang dari pct dengan ddof=1 — angka yang sama dengan Bagian 2: Menghitung Perubahan Harga — lalu pertanyaan berikutnya: apakah ketiga komoditas bergerak bersama? np.corrcoef menghitung korelasi Pearson antar variabel (Chapter Korelasi dan Kovarians); karena variabel tersusun sebagai kolom pct 359 × 3, parameter rowvar=False wajib disebut:
import numpy as np
# Pembangun deret harga 2025 (identik dengan Bagian 1).
rng = np.random.default_rng(42)
awal = np.array([15000.0, 45000.0, 18000.0])
drift = np.array([0.0003, 0.0012, 0.0004])
sigma = np.array([0.006, 0.030, 0.010])
musim = 1 - np.array([0.01, 0.15, 0.03]) * np.cos(2 * np.pi * np.arange(360) / 360)[:, None]
guncangan = drift + rng.normal(0.0, sigma, (360, 3))
harga = awal * np.cumprod(1 + guncangan, axis=0) * musim
pct = harga[1:] / harga[:-1] - 1
vol = pct.std(axis=0, ddof=1)
korelasi = np.corrcoef(pct, rowvar=False)
print("volatilitas :", np.round(vol, 4))
print("korelasi (baris & kolom: beras, cabai, minyak goreng):")
print(np.round(korelasi, 3))Output yang Dihasilkan:
volatilitas : [0.0062 0.029 0.0096]
korelasi (baris & kolom: beras, cabai, minyak goreng):
[[ 1. 0.063 -0.012]
[ 0.063 1. 0.059]
[-0.012 0.059 1. ]]Yang perlu dicermati:
pct.std(axis=0, ddof=1): volatilitas harian per komoditas (Chapter Variance dan Standard Deviation); cabai (0.0290) hampir lima kali beras (0.0062).np.corrcoef(pct, rowvar=False): matriks 3 × 3 yang simetris dengan diagonal 1; sel[i, j]mengorelasikan kolomidanj.- Semua angkanya dekat nol: beras–cabai 0.063, cabai–minyak 0.059, beras–minyak −0.012 — tidak ada pasangan yang bergerak terikat.
Moving Average 30 Hari #
Statistik di atas merangkum setahun sekaligus; analis juga memandang tren yang bergerak. Moving average 30 hari mengganti tiap hari dengan rata-rata 30 hari terakhirnya sehingga derau saling meredam — np.convolve menghitungnya sepanjang deret:
import numpy as np
# Pembangun deret harga 2025 (identik dengan Bagian 1).
rng = np.random.default_rng(42)
awal = np.array([15000.0, 45000.0, 18000.0])
drift = np.array([0.0003, 0.0012, 0.0004])
sigma = np.array([0.006, 0.030, 0.010])
musim = 1 - np.array([0.01, 0.15, 0.03]) * np.cos(2 * np.pi * np.arange(360) / 360)[:, None]
guncangan = drift + rng.normal(0.0, sigma, (360, 3))
harga = awal * np.cumprod(1 + guncangan, axis=0) * musim
bobot = np.ones(30) / 30
ma = np.empty((360 - 30 + 1, 3))
for k in range(3):
ma[:, k] = np.convolve(harga[:, k], bobot, mode="valid")
print("shape ma:", ma.shape)
print("cocok rata-rata 30 hari pertama:", np.allclose(ma[0], harga[:30].mean(axis=0)))
print("MA 30 hari terakhir:", np.round(ma[-1], 2))
print("harga hari terakhir:", np.round(harga[-1], 2))Output yang Dihasilkan:
shape ma: (331, 3)
cocok rata-rata 30 hari pertama: True
MA 30 hari terakhir: [16381.19 34690.5 16467.32]
harga hari terakhir: [16825.21 33284.1 16810.88]Uraian pentingnya:
bobot = np.ones(30) / 30: jendela berbobot sama rata; konvolusi harga dengan jendela ini adalah rata-rata bergerak.mode="valid": hanya titik berjendela penuh yang dihitung — 360 − 30 + 1 = 331 baris;"same"mengisi tepi deret dengan nol dan merusak nilainya.np.allclose(ma[0], harga[:30].mean(axis=0)): uji definisi — titik pertama MA memang rata-rata bulan pertama.- Bacaan: MA beras (16 381.19) di bawah harga penutupan (16 825.21) — ciri tren naik; MA cabai (34 690.50) dan minyak goreng (16 467.32) justru di atas harganya — keduanya sudah jauh dari puncak pertengahan tahun.
Ringkasan Statistik Tahunan #
Seluruh angka bagian ini terhimpun dalam satu tabel — src/statistik.py cukup menghasilkan tabel yang sama:
| Statistik | beras | cabai | minyak goreng |
|---|---|---|---|
| Rata-rata harga (Rp) | 15 615.85 | 39 532.07 | 18 347.36 |
| Median harga (Rp) | 15 653.81 | 38 487.91 | 18 072.53 |
| Harga terendah (Rp) | 14 764.39 | 30 267.24 | 16 042.13 |
| Hari terendah | 25 Maret | 13 November | 15 Desember |
| Harga tertinggi (Rp) | 16 825.21 | 52 830.47 | 21 901.49 |
| Hari tertinggi | 26 Desember | 23 April | 3 Juli |
| Volatilitas harian | 0.0062 | 0.0290 | 0.0096 |
| Korelasi dengan beras | 1.000 | 0.063 | -0.012 |
Tiga jebakan yang layak diingat:
ddof=1tetap dipakai agar volatilitas konsisten dengan Bagian 2: Menghitung Perubahan Harga; tanpa parameter itu angka sedikit menyusut.rowvar=Falsemudah terlupa: tanpa itupct359 × 3 dikorelasikan hari terhadap hari — matriks 359 × 359 yang lambat dan salah konteks.argminmemberi posisi, bukan nilai — nilai diambil denganharga[i, k]; MA mode"valid"juga memangkas 29 titik dari deret.
Dengan tabel ini masa lalu deret harga sudah terpetakan tuntas. Bagian 4: Membuat Simulasi Sederhana meminjam dua angkanya — drift terukur dan volatilitas — untuk melangkah 30 hari ke depan.