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:

python
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:

text
rata-rata : [15615.85 39532.07 18347.36]
median    : [15653.81 38487.91 18072.53]

Cara membacanya:

  1. harga.mean(axis=0) / np.median(harga, axis=0): keduanya mengagregasi sepanjang axis hari menjadi satu nilai per komoditas; median mengabaikan ujung-ujung sebaran.
  2. 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:

python
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:

text
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.49

Rincian mekanismenya:

  1. np.argmin(harga, axis=0): indeks baris harga terendah tiap kolom — [ 83 316 348]; argmax sama untuk tertinggi.
  2. harga[i_min[k], k] dan tanggal[i_min[k]]: nilai dan tanggal diambil pada indeks yang sama — datetime64 diindeks langsung dengan posisi hari (pola Chapter datetime64 dan timedelta64) — sehingga keduanya dijamin sinkron.
  3. 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:

python
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:

text
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:

  1. pct.std(axis=0, ddof=1): volatilitas harian per komoditas (Chapter Variance dan Standard Deviation); cabai (0.0290) hampir lima kali beras (0.0062).
  2. np.corrcoef(pct, rowvar=False): matriks 3 × 3 yang simetris dengan diagonal 1; sel [i, j] mengorelasikan kolom i dan j.
  3. 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:

python
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:

text
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:

  1. bobot = np.ones(30) / 30: jendela berbobot sama rata; konvolusi harga dengan jendela ini adalah rata-rata bergerak.
  2. mode="valid": hanya titik berjendela penuh yang dihitung — 360 − 30 + 1 = 331 baris; "same" mengisi tepi deret dengan nol dan merusak nilainya.
  3. np.allclose(ma[0], harga[:30].mean(axis=0)): uji definisi — titik pertama MA memang rata-rata bulan pertama.
  4. 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:

Statistikberascabaiminyak goreng
Rata-rata harga (Rp)15 615.8539 532.0718 347.36
Median harga (Rp)15 653.8138 487.9118 072.53
Harga terendah (Rp)14 764.3930 267.2416 042.13
Hari terendah25 Maret13 November15 Desember
Harga tertinggi (Rp)16 825.2152 830.4721 901.49
Hari tertinggi26 Desember23 April3 Juli
Volatilitas harian0.00620.02900.0096
Korelasi dengan beras1.0000.063-0.012

Tiga jebakan yang layak diingat:

  1. ddof=1 tetap dipakai agar volatilitas konsisten dengan Bagian 2: Menghitung Perubahan Harga; tanpa parameter itu angka sedikit menyusut.
  2. rowvar=False mudah terlupa: tanpa itu pct 359 × 3 dikorelasikan hari terhadap hari — matriks 359 × 359 yang lambat dan salah konteks.
  3. argmin memberi posisi, bukan nilai — nilai diambil dengan harga[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.