Tim pengawasan harga sebuah dinas perdagangan fiktif mencatat harga harian lima komoditas pangan di pasar tiga kota: Jakarta, Bandung, dan Makassar. Catatannya tersimpan sebagai satu berkas harga_komoditas.csv untuk 1 Januari sampai 31 Maret 2026. Seperti berkas lapangan pada umumnya, datanya belum rapi: ada harga tercatat dua kali, harga kelebihan satu angka nol, harga kosong, serta label kota dan satuan yang ditulis beragam. Sebelum menghitung kenaikan harga, kita perlu memuat berkas itu dengan benar dan tahu letak masalahnya. Pada bagian ini kita menyiapkan dataset, memuatnya dengan read_csv(), memeriksa strukturnya, lalu menyusun daftar masalah sebagai kontrak kerja Bagian 2.
Dataset Harga yang Dapat Direproduksi #
Dataset proyek ini dibuat oleh skrip, bukan diunduh dari internet, agar setiap pembaca bekerja pada data yang persis sama. Skrip buat_dataset.py membangun grid 90 hari x 5 komoditas x 3 kota yang berisi 1350 baris memakai np.random.default_rng(2026), lalu menyuntikkan masalah yang dirancang: 45 baris dihilangkan, enam harga salah ketik menjadi sepuluh kali lipat, sepuluh harga dikosongkan, 24 baris label kota dan 16 baris label satuan diubah bentuk penulisannya, serta delapan baris disalin sebagai duplikat. Hasil akhirnya 1313 baris mentah, pola dataset sintetis yang Anda kenal dari chapter Reproducible Data Processing.
| Kolom | Tipe di CSV | Isi |
|---|---|---|
tanggal | teks | tanggal pencatatan, 1 Januari sampai 31 Maret 2026 |
komoditas | teks | 5 komoditas, misalnya Cabai Merah Keriting |
kota | teks | Jakarta, Bandung, atau Makassar |
satuan | teks | kg atau liter |
harga | bilangan bulat | harga harian dalam rupiah per satuan |
Jalankan skrip berikut untuk melihat ukuran dataset yang dihasilkan.
"""Generator dataset harga harian komoditas pangan (proyek Analisis Harga Komoditas).
Dataset mentah dibuat secara deterministik (seed 2026) dan sengaja memuat
lima masalah kualitas data untuk dilatih:
1. baris duplikat,
2. harga ekstrem (kesalahan ketik angka nol tambahan),
3. harga kosong (NaN),
4. label kota dan satuan tidak seragam,
5. baris hilang (dua tanggal utuh dan lima pasangan tanggal-komoditas).
"""
from __future__ import annotations
import numpy as np
import pandas as pd
SEED = 2026
JUMLAH_HARI = 90
JUMLAH_DUPLIKAT = 8
# nama komoditas: (harga dasar, satuan, tren harian, volatilitas harian)
KOMODITAS = {
"Beras Premium": (15500, "kg", 1.0006, 0.006),
"Cabai Merah Keriting": (55000, "kg", 1.0000, 0.020),
"Bawang Merah": (38000, "kg", 1.0002, 0.008),
"Minyak Goreng Kemasan": (17500, "liter", 1.0001, 0.005),
"Gula Pasir": (18000, "kg", 1.0005, 0.006),
}
KOTA_FAKTOR = {"Jakarta": 1.005, "Bandung": 0.980, "Makassar": 1.015}
VARIAN_KOTA = {
"Jakarta": ["JAKARTA", "jakarta", " Jakarta "],
"Bandung": ["BANDUNG", "bandung", " Bandung "],
"Makassar": ["MAKASSAR", "makassar", " Makassar "],
}
VARIAN_SATUAN = {"kg": ["KG", "Kg", " kg "], "liter": ["LITER", "Liter", " liter "]}
def faktor_tren(nama: str) -> np.ndarray:
"""Faktor kumulatif harga per komoditas untuk 90 hari (indeks 0 = 1 Jan)."""
_, _, tren, _ = KOMODITAS[nama]
faktor = np.full(JUMLAH_HARI, tren)
if nama == "Cabai Merah Keriting":
faktor[32:49] = 1.035 # lonjakan pasokan 2-18 Februari
faktor[49:64] = 0.964 # koreksi bertahap 19 Februari - 5 Maret
return np.cumprod(faktor)
def buat_dataset() -> pd.DataFrame:
"""Menghasilkan DataFrame mentah 1313 baris dengan masalah yang dirancang."""
rng = np.random.default_rng(SEED)
tanggal = pd.date_range("2026-01-01", periods=JUMLAH_HARI, freq="D")
baris = []
for nama, (dasar, satuan, _, _) in KOMODITAS.items():
tren_kumulatif = faktor_tren(nama)
noise = rng.normal(0, KOMODITAS[nama][3], size=JUMLAH_HARI)
for i in range(JUMLAH_HARI):
for kota, faktor_kota in KOTA_FAKTOR.items():
harga = dasar * tren_kumulatif[i] * faktor_kota * (1 + noise[i])
baris.append((tanggal[i], nama, kota, satuan, max(1, round(harga))))
df = pd.DataFrame(
baris, columns=["tanggal", "komoditas", "kota", "satuan", "harga"]
)
# 1) baris hilang: dua tanggal utuh (30 baris) + lima pasangan
# tanggal-komoditas (15 baris), diambil dari tanggal interior
indeks_hilang = rng.choice(np.arange(10, 80), size=2, replace=False)
tanggal_hilang = tanggal[indeks_hilang]
df = df.loc[~df["tanggal"].isin(tanggal_hilang)].reset_index(drop=True)
hari_sisa = [i for i in range(10, 80) if tanggal[i] not in set(tanggal_hilang)]
pilih = sorted(rng.choice(len(hari_sisa) * 5, size=5, replace=False))
for p in pilih:
i, j = divmod(p, 5)
nama = list(KOMODITAS)[j]
df = df.loc[
~((df["tanggal"] == tanggal[hari_sisa[i]]) & (df["komoditas"] == nama))
]
df = df.reset_index(drop=True)
# satu permutasi dipakai bersama agar baris bermasalah tidak tumpang tindih
pilih = rng.permutation(len(df))
iks_outlier = pilih[:6]
iks_nan = pilih[6:16]
iks_duplikat = pilih[16 : 16 + JUMLAH_DUPLIKAT]
# 2) harga ekstrem: kesalahan ketik angka nol tambahan (6 baris)
df.loc[iks_outlier, "harga"] = df.loc[iks_outlier, "harga"] * 10
# 3) harga kosong (10 baris)
df.loc[iks_nan, "harga"] = np.nan
# 4a) label kota tidak seragam (8 baris per kota)
for kota in KOTA_FAKTOR:
baris_kota = df.index[df["kota"] == kota].to_numpy()
iks = rng.choice(baris_kota, size=8, replace=False)
for n, i in enumerate(iks):
df.at[i, "kota"] = VARIAN_KOTA[kota][n % 3]
# 4b) label satuan tidak seragam (8 baris per satuan)
for satuan in ("kg", "liter"):
baris_satuan = df.index[df["satuan"] == satuan].to_numpy()
iks = rng.choice(baris_satuan, size=8, replace=False)
for n, i in enumerate(iks):
df.at[i, "satuan"] = VARIAN_SATUAN[satuan][n % 3]
# 5) baris duplikat (8 salinan persis)
df = pd.concat([df, df.loc[iks_duplikat]], ignore_index=True)
urutan = rng.permutation(len(df))
return df.iloc[urutan].reset_index(drop=True)
if __name__ == "__main__":
import tempfile
from pathlib import Path
df = buat_dataset()
with tempfile.TemporaryDirectory() as folder:
berkas = Path(folder) / "harga_komoditas.csv"
df.to_csv(berkas, index=False)
print(
f"harga_komoditas.csv siap: {df.shape[0]} baris x {df.shape[1]} kolom "
f"({berkas.stat().st_size} byte)"
)Output yang Dihasilkan:
harga_komoditas.csv siap: 1313 baris x 5 kolom (61937 byte)Fungsi buat_dataset() mengembalikan DataFrame 1313 baris: 1305 kombinasi unik tanggal-komoditas-kota ditambah delapan salinan persis. Blok if __name__ == "__main__" di bagian bawah skrip menuliskan dataset ke harga_komoditas.csv di dalam tempfile.TemporaryDirectory() hanya untuk melaporkan ukurannya; folder sementara itu terhapus otomatis sehingga tidak ada berkas tertinggal di folder kerja Anda.
Memuat CSV dan Memeriksa Struktur #
Sekarang kita memperlakukan dataset seperti berkas lapangan sungguhan: tulis ke CSV, muat ulang dengan pd.read_csv(), lalu periksa strukturnya. Skrip berikut mencetak bentuk data, tipe tiap kolom, lima baris pertama, statistik ringkas harga, dan rekap masalah yang terdeteksi.
"""Memuat harga_komoditas.csv lalu memeriksa struktur dan masalah awal dataset."""
from pathlib import Path
import tempfile
import pandas as pd
from buat_dataset import buat_dataset
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as folder:
berkas = Path(folder) / "harga_komoditas.csv"
buat_dataset().to_csv(berkas, index=False)
df = pd.read_csv(berkas)
print("== Struktur dataset ==")
print(f"bentuk: {df.shape[0]} baris x {df.shape[1]} kolom")
print(df.dtypes.to_string())
print("\n== Lima baris pertama ==")
print(df.head(5).to_string(index=False))
print("\n== Statistik ringkas kolom harga (dibulatkan 2 desimal) ==")
print(df["harga"].describe().round(2).to_string())
median = df.groupby("komoditas")["harga"].transform("median")
pasangan = df.drop_duplicates().groupby(["tanggal", "komoditas"]).size()
print("\n== Masalah yang ditemukan ==")
print(f"baris duplikat : {df.duplicated().sum()}")
print(f"harga kosong (NaN) : {df['harga'].isna().sum()}")
print(f"harga ekstrem (> 2x median komoditas) : {(df['harga'] > 2 * median).sum()}")
print(f"bentuk penulisan kota : {df['kota'].nunique()}")
print(f"bentuk penulisan satuan : {df['satuan'].nunique()}")
print(f"tanggal unik : {df['tanggal'].nunique()} dari 90 hari")
print(f"pasangan tanggal-komoditas lengkap : {(pasangan == 3).sum()} dari 450")Output yang Dihasilkan:
== Struktur dataset ==
bentuk: 1313 baris x 5 kolom
tanggal str
komoditas str
kota str
satuan str
harga float64
== Lima baris pertama ==
tanggal komoditas kota satuan harga
2026-01-26 Cabai Merah Keriting Makassar kg 57683.0
2026-01-23 Bawang Merah Bandung kg 37422.0
2026-03-28 Beras Premium Jakarta kg 16347.0
2026-02-06 Beras Premium Jakarta kg 15877.0
2026-02-21 Bawang Merah Makassar kg 39479.0
== Statistik ringkas kolom harga (dibulatkan 2 desimal) ==
count 1303.00
mean 31835.04
std 27098.20
min 15044.00
25% 17318.00
50% 18432.00
75% 38864.00
max 570240.00
== Masalah yang ditemukan ==
baris duplikat : 8
harga kosong (NaN) : 10
harga ekstrem (> 2x median komoditas) : 6
bentuk penulisan kota : 12
bentuk penulisan satuan : 8
tanggal unik : 88 dari 90 hari
pasangan tanggal-komoditas lengkap : 435 dari 450Mari kita telaah bagian-bagian pentingnya:
df.shapedandf.dtypes: hasilnya 1313 baris x 5 kolom. Empat kolom teks bertipestr— ini perilaku bawaan pandas 3.0, bukan lagiobjectseperti pandas 2.x — sementarahargaterbacafloat64.- Tipe
float64padaharga: meskipun harga di CSV berupa bilangan bulat, sepuluh sel kosong memaksa pandas memakaifloat64karenaNaNhanya tersedia pada tipe float; kolom angka dengan nilai kosong selalu terbacafloat64. df.head(5): urutan tanggalnya acak —2026-01-26lalu2026-01-23— bukti baris perlusort_values()sebelum analisis deret waktu pada Bagian 3.describe():maxharga570240.00hampir sepuluh kali lipat mediannya18432.00— inilah harga ekstrem buatan kesalahan ketik. Bariscountyang hanya1303dari1313mengonfirmasi sepuluh harga kosong.- Deteksi harga ekstrem:
transform("median")membandingkan tiap harga dengan median komoditasnya sendiri; harga di atas dua kali lipat median dihitung ekstrem, sehingga komoditas mahal seperti cabai tidak dicap sebagai outlier.
Daftar Masalah yang Terdeteksi #
Rekap di akhir output merangkum temuan dalam angka pasti:
| Masalah | Jumlah | Cara deteksi |
|---|---|---|
| Baris duplikat | 8 | df.duplicated() |
| Harga kosong (NaN) | 10 | df["harga"].isna() |
| Harga ekstrem | 6 | pembandingan dengan median per komoditas |
| Label kota tidak seragam | 12 bentuk | df["kota"].nunique() |
| Label satuan tidak seragam | 8 bentuk | df["satuan"].nunique() |
| Baris hilang | 45 kombinasi | pasangan tanggal-komoditas lengkap hanya 435 dari 450 |
Enam kelompok masalah ini menjadi target kerja Bagian 2: delapan baris duplikat dihapus, 12 bentuk label kota dan 8 bentuk label satuan diseragamkan, kolom tanggal dikonversi menjadi datetime64, enam harga ekstrem dipotong ke acuan harian, dan sepuluh harga kosong diisi dari harga terakhir. Teori tiap teknik sudah dibahas pada section Data Cleaning serta section Missing Data dan Outlier Lanjutan; di proyek ini kita fokus merangkainya menjadi satu prosedur utuh.
Sumber bacaan resmi: pandas.read_csv , pandas.DataFrame.duplicated , dan pandas.DataFrame.describe .