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:

MasalahPerlakuanAlasan
W10 tercatat tiga kalibuang baris kembar, pertahankan entri pertamasatu minggu cukup satu baris; ketiga baris W10 isinya identik
Sel - pada W27interpolasi linear antara W26 dan W28deret perlu satu baris utuh per minggu; membuang baris merusak selang 7 hari
Kolom harga bertipe objectkonversi 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:

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

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

Mari kita baca hasilnya langkah demi langkah:

  1. 38 menjadi 36 baris: dua baris kembar W10 hilang, tersisa satu. Jumlah baris kini sama dengan jumlah minggu — syarat pertama deret yang layak dipakai.
  2. 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.
  3. 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:

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

text
             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.5

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