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,18300

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

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

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

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

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

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

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

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

text
mean mentah  : 17241.67   std sampel : 1644.73
bulan ke-20 : harga 24000, z = 4.11
pengganti    : 17850.00

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

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

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

Pembersihan 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.