Bagian 3 ditutup dengan tabel masalah beserta perlakuan yang direncanakan; pada bagian ini kita mengeksekusinya. Pembersihan untuk prediksi lebih ketat daripada pembersihan untuk laporan biasa: setiap baris adalah tetangga waktu bagi baris lain, sehingga satu baris yang dibuang atau salah tempat dapat mengubah makna fitur yang akan kita bangun pada Bagian 6. Karena itu setiap langkah disertai alasannya, dan hasil akhirnya diverifikasi ulang sampai lolos daftar periksa — bukan sekadar dilihat sekilas lalu dipercaya.
Tiga Perlakuan dan Alasannya #
Rencana dari Bagian 3 memuat tiga masalah: W10 tercatat tiga kali, satu sel harga W27 tertulis -, dan kolom harga terbaca sebagai teks. Ketiganya kita rawat dengan perlakuan berikut:
| Masalah | Perlakuan | Alasan |
|---|---|---|
| W10 tercatat tiga kali | buang baris kembar, pertahankan entri pertama | satu minggu cukup satu baris; ketiga baris W10 isinya identik |
Sel - pada W27 | interpolasi linear antara W26 dan W28 | deret perlu satu baris utuh per minggu; membuang baris merusak selang 7 hari |
Kolom harga bertipe object | konversi ke numerik dengan errors="coerce" | operasi matematika hanya berjalan pada kolom angka |
drop_duplicates(subset=["minggu"], keep="first") mempertahankan entri pertama untuk setiap minggu — pilihan yang aman karena ketiga baris W10 memuat nilai yang sama persis. pd.to_numeric(..., errors="coerce") mengubah sel yang gagal dikonversi (termasuk -) menjadi NaN, penanda “tidak ada data” milik pandas, alih-alih menggagalkan seluruh konversi. Barulah setelah itu interpolate() mengisi NaN dari tetangganya.
Menjalankan Resep Pembersihan #
Empat langkah tersebut dirangkai menjadi satu resep yang juga dipakai pipeline utuh pada src/prediksi_harga.py:
import pandas as pd
df = pd.read_csv("../data/harga_mingguan.csv")
print(f"Data mentah : {df.shape[0]} baris, kolom harga bertipe {df['harga_ribu'].dtype}")
# 1. Buang duplikat: satu minggu cukup satu baris (entri pertama dipertahankan).
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
print(f"Setelah buang duplikat : {len(bersih)} baris (2 baris kembar W10 dibuang)")
# 2. Konversi kolom harga ke angka; sel '-' berubah menjadi NaN.
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
kosong = bersih.loc[bersih["harga_ribu"].isna(), "minggu"].tolist()
print(f"Sel kosong (NaN) : {int(bersih['harga_ribu'].isna().sum())} pada minggu {kosong}")
# 3. Urutkan menurut tanggal mulai, lalu rapikan indeks.
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
sebelum = bersih.set_index("minggu")["harga_ribu"]
print(f"Tetangga W27 : W26 = {sebelum['W26']}, W28 = {sebelum['W28']}")
# 4. Isi NaN dengan interpolasi linear, bulatkan 1 desimal seperti data aslinya.
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
sesudah = bersih.set_index("minggu")["harga_ribu"]
print(f"Setelah interpolasi : {len(bersih)} baris, W27 = {sesudah['W27']}")
print(f"Tipe kolom harga akhir : {bersih['harga_ribu'].dtype}")Output yang Dihasilkan:
Data mentah : 38 baris, kolom harga bertipe object
Setelah buang duplikat : 36 baris (2 baris kembar W10 dibuang)
Sel kosong (NaN) : 1 pada minggu ['W27']
Tetangga W27 : W26 = 42.2, W28 = 40.5
Setelah interpolasi : 36 baris, W27 = 41.4
Tipe kolom harga akhir : float64Mari kita baca hasilnya langkah demi langkah:
- 38 menjadi 36 baris: dua baris kembar W10 hilang, tersisa satu. Jumlah baris kini sama dengan jumlah minggu — syarat pertama deret yang layak dipakai.
- W27 menjadi NaN lalu terisi: interpolasi linear mengambil titik tengah W26 (42,2) dan W28 (40,5), yaitu 41,35, lalu
round(1)membulatkannya menjadi 41,4 — satu desimal, konsisten dengan presisi seluruh data. Pembulatan ini membuat 41,4 sedikit di atas titik tengah matematis 41,35. - Tipe berubah menjadi
float64: kolom harga kini benar-benar angka; rata-rata, regresi, dan prediksi bisa berjalan.
Mengapa Interpolasi, Bukan Membuang Baris #
Pada data tabular biasa, membuang satu baris bermasalah sering menjadi pilihan paling aman. Pada deret waktu pilihan itu mahal: bila W27 dibuang, W28 kehilangan tetangga kirinya — lag_1 milik W28 akan berisi harga W26, dan label “harga satu minggu sebelumnya” menjadi bohong. Selang 7 hari yang sudah kita periksa pada Bagian 3 juga rusak, sementara fitur minggu_ke bergeser untuk seluruh minggu berikutnya.
Interpolasi linear adalah taksiran, bukan catatan pasar yang sesungguhnya. Untuk deret yang bergerak perlahan seperti harga dua minggu di sekitar W27 (42,2 dan 40,5), taksiran titik tengah wajar digunakan — dengan catatan kejujuran bahwa nilai 41,4 itu hasil hitungan. Catatan ini kita bawa sampai laporan akhir pada seksi keterbatasan.
Memverifikasi Hasil Pembersihan #
Pembersihan bisa saja memperkenalkan masalah baru: kesalahan urutan, pembulatan yang tidak sengaja, atau baris yang lolos. Karena itu resep yang sama kita jalankan ulang, lalu hasilnya diperiksa dengan tujuh syarat:
import pandas as pd
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
# Pemeriksaan ulang: data bersih harus lolos tujuh syarat sebelum dipakai.
nomor = bersih["minggu"].str.extract(r"(\d+)")[0].astype(int)
tanggal = pd.to_datetime(bersih["tanggal_mulai"])
pemeriksaan = pd.DataFrame(
{
"pemeriksaan": [
"jumlah baris",
"nomor minggu utuh W1-W36",
"tidak ada minggu ganda",
"tidak ada sel kosong",
"selang tetap 7 hari",
"tipe kolom harga",
"rata-rata harga (ribu)",
],
"hasil": [
len(bersih),
sorted(nomor.tolist()) == list(range(1, 37)),
bersih["minggu"].is_unique,
int(bersih["harga_ribu"].isna().sum()) == 0,
bool((tanggal.diff().dropna() == pd.Timedelta(days=7)).all()),
str(bersih["harga_ribu"].dtype),
round(bersih["harga_ribu"].mean(), 2),
],
}
)
print(pemeriksaan.to_string(index=False))
print()
print("Baris bekas masalah, sesudah pembersihan:")
print(bersih[bersih["minggu"].isin(["W9", "W10", "W11", "W26", "W27", "W28"])].to_string(index=False))Output yang Dihasilkan:
pemeriksaan hasil
jumlah baris 36
nomor minggu utuh W1-W36 True
tidak ada minggu ganda True
tidak ada sel kosong True
selang tetap 7 hari True
tipe kolom harga float64
rata-rata harga (ribu) 40.56
Baris bekas masalah, sesudah pembersihan:
minggu tanggal_mulai harga_ribu
W9 2025-07-28 39.9
W10 2025-08-04 41.4
W11 2025-08-11 40.7
W26 2025-11-24 42.2
W27 2025-12-01 41.4
W28 2025-12-08 40.5Tujuh baris pertama adalah daftar periksa kelayakan: jumlah baris tepat 36, nomor minggu utuh W1–W36 tanpa lompatan, tidak ada minggu ganda, tidak ada sel kosong, selang antar minggu tetap 7 hari, tipe kolom angka, dan rata-rata 40,56 — bergeser tipis dari 40,59 pada data mentah karena bobot ganda W10 sudah hilang. Tabel kedua memperlihatkan bekas masalah yang kini sehat: W10 tampil satu kali, dan W27 berdiri utuh dengan nilai 41,4 di antara kedua tetangganya. Berbagai pilihan lain untuk mengisi nilai kosong dibahas pada panduan pandas tentang missing data ; kita memilih interpolasi linear karena satu-satunya alasan yang relevan di sini: menjaga kesinambungan waktu.
Ringkasan Bagian 4 #
Data mentah 38 baris kini menjadi 36 baris bersih: duplikat W10 dibuang, sel - W27 diisi interpolasi linear (41,4) dengan catatan yang jujur, kolom harga kembali bertipe angka, dan seluruh verifikasi lolos. Deret harga siap dibaca — tren, musiman, dan lonjakan menunggu untuk dikenali sebelum menjadi bahan fitur.
Pembacaan itulah pekerjaan Bagian 5: Eksplorasi Deret Waktu .