Data komoditas tiba dalam kondisi yang tidak siap analisis: satu sel kosong dan satu nilai yang mencurigakan. Keduanya bukan halangan melainkan bagian dari pekerjaan — hampir tidak ada dataset nyata yang datang bersih, dan langkah pertama analis selalu sama: muat, periksa, perbaiki, catat. Pada bagian ini kita menjalankan alur pembersihan itu dari awal hingga menghasilkan dataset bersih 24 bulan yang menjadi dasar seluruh analisis pada bagian berikutnya, dengan setiap keputusan dihitung dan didokumentasikan.
Berkas Data Mentah #
Berkas harga_komoditas.csv memuat 24 baris data bulanan — berikut isi lengkapnya:
bulan,beras,gula_pasir,minyak_goreng
2025-01,12500,16000,15600
2025-02,12800,16200,15800
2025-03,12700,16100,15700
2025-04,13100,16400,16000
2025-05,13400,16600,16200
2025-06,13300,16500,16100
2025-07,13600,16800,16400
2025-08,13900,17000,16600
2025-09,13800,16900,16500
2025-10,14200,17200,16800
2025-11,14500,17400,17000
2025-12,14400,17300,16900
2026-01,14600,17500,17100
2026-02,14500,,17200
2026-03,14800,17800,17400
2026-04,15000,18000,17300
2026-05,14900,17900,17500
2026-06,15200,18200,17600
2026-07,15400,18400,17800
2026-08,15600,18600,24000
2026-09,15500,18500,17900
2026-10,15800,18800,18100
2026-11,16000,19000,18000
2026-12,15900,18900,18300Dua masalah terlihat bahkan sebelum kode dijalankan. Baris 2026-02 memiliki sel gula pasir yang kosong — dua koma berturut-turut tanpa angka di antaranya. Dan baris 2026-08 mencatat minyak goreng 24000, jauh melonjak dari tetangganya yang berkisar di 17800-an; lompatan sebesar itu dalam satu bulan, yang kemudian langsung kembali normal, adalah pola khas galat pencatatan atau kesalahan input. Baris beras tahun 2025 persis deret chapter Pengenalan Time Series, sehingga mean tahun pertamanya nanti akan berbunyi 13516.67 Rp/kg — angka yang sudah pernah kita hitung.
Membaca dan Memeriksa #
Pembacaan berkas di disk tetap satu baris pandas:
import pandas as pd
df = pd.read_csv("harga_komoditas.csv")Jalankan dari folder yang sama dengan harga_komoditas.csv — sel kosong akan dibaca sebagai NaN dan kolom gula pasir otomatis menjadi float, persis perilaku yang diperkenalkan chapter Analisis Data dengan pandas. Cuplikan berikut menampilkan 13 baris yang mencakup kedua masalah tersebut. Seluruh 24 baris tertanam di dalam teks StringIO, sehingga cuplikan dapat dijalankan tanpa bergantung pada lokasi berkas:
import pandas as pd
from io import StringIO
teks = """bulan,beras,gula_pasir,minyak_goreng
2025-01,12500,16000,15600
2025-02,12800,16200,15800
2025-03,12700,16100,15700
2025-04,13100,16400,16000
2026-01,14600,17500,17100
2026-02,14500,,17200
2026-03,14800,17800,17400
2026-04,15000,18000,17300
2026-05,14900,17900,17500
2026-06,15200,18200,17600
2026-07,15400,18400,17800
2026-08,15600,18600,24000
2026-09,15500,18500,17900
"""
df = pd.read_csv(StringIO(teks))
print(df)
print()
print(df.isna().sum())Output yang Dihasilkan:
bulan beras gula_pasir minyak_goreng
0 2025-01 12500 16000.0 15600
1 2025-02 12800 16200.0 15800
2 2025-03 12700 16100.0 15700
3 2025-04 13100 16400.0 16000
4 2026-01 14600 17500.0 17100
5 2026-02 14500 NaN 17200
6 2026-03 14800 17800.0 17400
7 2026-04 15000 18000.0 17300
8 2026-05 14900 17900.0 17500
9 2026-06 15200 18200.0 17600
10 2026-07 15400 18400.0 17800
11 2026-08 15600 18600.0 24000
12 2026-09 15500 18500.0 17900
bulan 0
beras 0
gula_pasir 1
minyak_goreng 0
dtype: int64Inspeksi mengonfirmasi kedua kejadian. isna().sum() melaporkan tepat satu sel kosong, dimiliki kolom gula_pasir — baris 2026-02 tampil sebagai NaN sementara kolom lain utuh. Nilai 24000 pada 2026-08 tidak ditandai apa pun oleh isna karena secara teknis bukan data hilang; ia adalah data yang salah, dan harus ditangkap dengan alat yang berbeda. Perhatikan juga format kolom bulan — label 2025-01 sampai 2026-12 — yang dibaca sebagai teks dan cukup dipakai sebagai penanda urutan pada proyek ini.
Mengisi Sel Kosong dengan Interpolasi #
Sel kosong pada deret waktu sebaiknya jangan diisi sembarangan. Strategi dari chapter pandas — mengisi dengan mean kolom — kurang tepat di sini karena harga sedang bertren naik; mean dua tahun justru terpaut jauh dari level Februari 2026. Cara yang lebih menghormati urutan waktu adalah interpolasi: ambil rata-rata dua tetangga terdekat, satu sebelum dan satu sesudah sel yang kosong:
gula_mentah = [16000, 16200, 16100, 16400, 16600, 16500, 16800, 17000, 16900, 17200, 17400, 17300,
17500, None, 17800, 18000, 17900, 18200, 18400, 18600, 18500, 18800, 19000, 18900]
posisi = [i for i, v in enumerate(gula_mentah) if v is None]
print("sel kosong di indeks :", posisi)
i = posisi[0]
sebelum, sesudah = gula_mentah[i - 1], gula_mentah[i + 1]
isi = (sebelum + sesudah) / 2
print(f"tetangga : {sebelum} dan {sesudah}")
print(f"interpolasi : {isi:.2f}")
gula = [isi if v is None else v for v in gula_mentah]
print("gula bulan 13-15 :", gula[12:15])Output yang Dihasilkan:
sel kosong di indeks : [13]
tetangga : 17500 dan 17800
interpolasi : 17650.00
gula bulan 13-15 : [17500, 17650.0, 17800]Indeks 13 adalah baris ke-14 — bulan 2026-02 — dengan tetangga Januari (17500) dan Maret (17800), sehingga interpolasinya (17500 + 17800) / 2 = 17650.00. Baris terakhir memverifikasi hasilnya: tiga bulan di sekitar sel yang diperbaiki kini berjalan mulus 17500, 17650, 17800 — angka pengisi tepat berada di tengah dua tetangganya. Deret gula pasir kini utuh 24 angka dan siap dipakai pada bagian-bagian berikutnya.
Menandai Anomali dengan Z-Score #
Nilai 24000 harus ditangkap dengan ukuran, bukan dengan kesan. Alat yang dipakai adalah z-score dari chapter Z-Score: jarak nilai ke mean deret, dalam satuan standar deviasi. Cuplikan berikut menghitung mean dan std sampel seluruh 24 nilai minyak goreng mentah, lalu menandai bulan yang z-nya melampaui ambang 2:
import statistics
minyak_mentah = [15600, 15800, 15700, 16000, 16200, 16100, 16400, 16600, 16500, 16800, 17000, 16900,
17100, 17200, 17400, 17300, 17500, 17600, 17800, 24000, 17900, 18100, 18000, 18300]
mean = statistics.mean(minyak_mentah)
s = statistics.stdev(minyak_mentah)
print(f"mean mentah : {mean:.2f} std sampel : {s:.2f}")
for i, v in enumerate(minyak_mentah, start=1):
z = (v - mean) / s
if abs(z) > 2:
print(f"bulan ke-{i:2d} : harga {v}, z = {z:.2f}")
pengganti = (minyak_mentah[18] + minyak_mentah[20]) / 2
print(f"pengganti : {pengganti:.2f}")Output yang Dihasilkan:
mean mentah : 17241.67 std sampel : 1644.73
bulan ke-20 : harga 24000, z = 4.11
pengganti : 17850.00Satu-satunya bulan yang ditandai adalah bulan ke-20 — Agustus 2026 — dengan z = 4.11, lebih dari empat standar deviasi di atas mean deret. Tidak ada bulan lain yang bahkan mendekati ambang; nilai tertinggi berikutnya berada dalam rentang wajar deret yang sedang bertren. Angka 24000 hampir pasti galat pencatatan, dan pengganti yang wajar adalah rata-rata dua tetangganya: Juli (17800) dan September (17900), menghasilkan 17850.00 — level yang konsisten dengan kelanjutan tren. Nilai pengganti memang hasil perkiraan, dan itulah sebabnya ia wajib dicatat dalam laporan, bukan didiamkan.
Dataset Bersih dan Statistiknya #
Kedua perbaikan kini diterapkan dan dataset bersih siap. Cuplikan berikut merangkum statistik ketiga komoditas dari 24 nilai bersih masing-masing — angka-angka ini menjadi acuan bagi seluruh bagian berikutnya:
import statistics
beras = [12500, 12800, 12700, 13100, 13400, 13300, 13600, 13900, 13800, 14200, 14500, 14400,
14600, 14500, 14800, 15000, 14900, 15200, 15400, 15600, 15500, 15800, 16000, 15900]
gula = [16000, 16200, 16100, 16400, 16600, 16500, 16800, 17000, 16900, 17200, 17400, 17300,
17500, 17650, 17800, 18000, 17900, 18200, 18400, 18600, 18500, 18800, 19000, 18900]
minyak = [15600, 15800, 15700, 16000, 16200, 16100, 16400, 16600, 16500, 16800, 17000, 16900,
17100, 17200, 17400, 17300, 17500, 17600, 17800, 17850, 17900, 18100, 18000, 18300]
for nama, data in [("beras", beras), ("gula_pasir", gula), ("minyak_goreng", minyak)]:
print(f"{nama:<13s}: mean {statistics.mean(data):8.2f} | std {statistics.stdev(data):7.2f} | min {min(data)} | max {max(data)}")Output yang Dihasilkan:
beras : mean 14391.67 | std 1066.04 | min 12500 | max 16000
gula_pasir : mean 17485.42 | std 934.54 | min 16000 | max 19000
minyak_goreng: mean 16985.42 | std 816.60 | min 15600 | max 18300Pembersihan selesai dengan angka yang bisa dipercaya. Mean minyak goreng turun dari 17241.67 (versi mentah) ke 16985.42 — anomali 24000 yang tadinya menggelembungkan rata-rata kini tidak lagi bekerja — dan std-nya mengecil dari 1644.73 ke 816.60, hampir separuhnya. Perhatikan juga std beras (1066.04) yang jauh terbesar di antara ketiganya meskipun meannya terkecil: beras naik paling banyak sepanjang dua tahun, sehingga jarak harga awal ke harga akhir ikut melebarkan sebarannya — petunjuk pertama bahwa beras akan menjadi komoditas paling fluktuatif. Dengan data bersih di tangan, Bagian 2 mulai mengukur pergerakannya.