Tim pemantauan pasar di kantor perdagangan meminta kita menganalisis pergerakan harga tiga komoditas pokok sepanjang 2025: beras, cabai, dan minyak goreng. Data harga asli bersifat internal, maka kita membangun deret sintetis yang meniru perilakunya — tren pelan, musim yang mengangkat harga di pertengahan tahun, dan guncangan harian. Deret harga dibangun sebagai random walk: harga hari ini adalah harga awal yang dikalikan seluruh faktor harian (1 + drift + guncangan) secara beruntun, dan np.cumprod dari Chapter Cumulative Sum dan Product melakukannya tanpa satu pun loop. Karena seluruh keacakan keluar dari np.random.default_rng(42), deret yang sama muncul persis di komputer mana pun (Chapter Reproducibility dan Random Seed).

Bagian ini menyiapkan dua bahan yang dipakai tiga bagian berikutnya: matriks harga 360 × 3 yang tersimpan di data/harga_komoditas.csv, dan deret tanggal datetime64 yang sejajar baris demi baris dengan harganya. Pada proyek sungguhan, langkah pembangunan ini kita kemas dalam src/persiapan_harga.py.

Membangun Deret Harga Sintetis #

Tiga komoditas diberi karakter berbeda: beras bergerak pelan, cabai sangat liar, minyak goreng di tengah-tengah. Perbedaan itu lahir dari dua vektor parameter — drift (arah tren harian) dan sigma (simpangan baku guncangan harian):

python
import numpy as np

# Pembangun deret harga 2025 — dipakai di seluruh proyek.
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("shape:", harga.shape, "| dtype:", harga.dtype)
print("harga hari pertama :", np.round(harga[0], 2))
print("harga hari terakhir:", np.round(harga[-1], 2))

Output yang Dihasilkan:

text
shape: (360, 3) | dtype: float64
harga hari pertama : [14881.61 37102.52 17598.01]
harga hari terakhir: [16825.21 33284.1  16810.88]

Mari kita bedah pembangun deret ini:

  1. Baris 4-6 (awal, drift, sigma): tiga array sepanjang 3 menyimpan harga pembukaan (rupiah), tren harian, dan volatilitas per komoditas; urutan kolomnya tetap: beras, cabai, minyak goreng.
  2. Baris 7 (musim = ...): faktor musiman bentuk 360 × 3; np.cos menyelesaikan satu siklus penuh sepanjang 360 hari, amplitudo per komoditas (0.01, 0.15, 0.03) membuat cabai paling terpengaruh, dan tanda minus menaruh lembah musiman di awal tahun.
  3. Baris 8 (guncangan = ...): rng.normal(0.0, sigma, (360, 3)) menggambar derau standar lalu menskalakannya per kolom, ditambah drift — satu matriks berisi seluruh perubahan harian.
  4. Baris 9 (np.cumprod(1 + guncangan, axis=0)): perkalian beruntun 360 faktor menghasilkan lintasan harga; mengalikannya dengan awal menempatkan lintasan pada level rupiah, dan * musim menempelkan gelombang musiman di atasnya.
  5. Baris 11-13: harga hari pertama lebih rendah dari harga pembukaan karena faktor musiman awal tahun berada di lembah; cabai ditutup tahun itu di 33 284 — guncangan harian yang besar bisa melampaui arah tren dalam jangka setahun.

Deret harga disimpan ke CSV agar serupa dengan data yang keluar dari sistem kasir, sementara tanggalnya tidak ikut disimpan sebagai angka — kita membuatnya terpisah dari datetime64:

python
import numpy as np

# Pembangun deret harga 2025 (identik dengan blok sebelumnya).
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

np.savetxt("harga_komoditas.csv", harga, delimiter=",", fmt="%.2f",
           header="beras,cabai,minyak_goreng", comments="")
harga_csv = np.loadtxt("harga_komoditas.csv", delimiter=",", skiprows=1)
print("identik persis:", np.array_equal(harga, harga_csv))
print("allclose atol=0.01:", np.allclose(harga, harga_csv, atol=0.01))
tanggal = np.arange(np.datetime64("2025-01-01"), np.datetime64("2025-12-27"))
print("tanggal:", tanggal[0], "s.d.", tanggal[-1], "| jumlah:", tanggal.size)
with open("harga_komoditas.csv") as f:
    for i, baris in enumerate(f):
        if i >= 2:
            break
        print(baris.rstrip())

Output yang Dihasilkan:

text
identik persis: False
allclose atol=0.01: True
tanggal: 2025-01-01 s.d. 2025-12-26 | jumlah: 360
beras,cabai,minyak_goreng
14881.61,37102.52,17598.01

Rincian yang penting:

  1. savetxt dengan fmt="%.2f": harga dibulatkan dua desimal; header menuliskan nama kolom dan comments="" meniadakan tanda # di depannya (Chapter Membaca dan Menulis CSV).
  2. array_equal bernilai False, allclose bernilai True: pembulatan dua desimal menggeser nilai paling jauh setengah sen, sehingga kedua array tidak identik bit demi bit tetapi setara untuk analisis harga.
  3. np.arange(datetime64, datetime64): 360 tanggal harian dari 1 Januari sampai 26 Desember 2025; deret ini disebut kolom tanggal pendamping karena sejajar persis dengan baris harga tanpa ikut masuk berkas CSV (pola datetime64 dibahas pada Chapter datetime64 dan timedelta64).

Pemeriksaan Struktur Awal #

Terakhir, kita pastikan deret layak dianalisis: tiga kolom, nol nan, tidak ada harga nol atau negatif, dan rentang nilai yang masuk akal untuk pasar:

python
import numpy as np

# Pembangun deret harga 2025 (identik dengan blok sebelumnya).
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("min per komoditas:", np.round(harga.min(axis=0), 2))
print("max per komoditas:", np.round(harga.max(axis=0), 2))
print("semua harga > 0 :", bool(np.all(harga > 0)))
print("ada nan         :", bool(np.isnan(harga).any()))

Output yang Dihasilkan:

text
min per komoditas: [14764.39 30267.24 16042.13]
max per komoditas: [16825.21 52830.47 21901.49]
semua harga > 0 : True
ada nan         : False

Pemeriksaan ini memakai pola agregasi axis=0 (Chapter Operasi Berdasarkan Axis):

  1. harga.min(axis=0) dan harga.max(axis=0): satu nilai per komoditas; cabai menyentuh 52 830 di puncak musim dan turun sampai 30 267 — rentang yang jauh lebih lebar daripada beras.
  2. np.all(harga > 0): model perkalian menjamin harga positif, dan pemeriksaan ini membuktikannya pada data nyata — kebiasaan yang wajib dipertahankan ketika model diganti data lapangan.
  3. np.isnan(harga).any(): tidak ada sel kosong; bool(...) mengubah hasil numpy.bool_ menjadi True/False Python murni agar outputnya rapi.

Ringkasan dataset yang dipakai sepanjang proyek:

KolomKomoditasHarga pembukaan (Rp)Rentang teramati (Rp)
0beras15 00014 764 - 16 825
1cabai45 00030 267 - 52 830
2minyak goreng18 00016 042 - 21 901

Data siap di data/harga_komoditas.csv, tanggal siap di memori. Bagian 2: Menghitung Perubahan Harga mulai mengolah deret ini — dan di sanalah informasi paling berharga dari harga justru muncul: bukan pada levelnya, melainkan pada perubahannya.