Episode ini membahas pekerjaan yang memakan 60-80% waktu analis: data cleaning. Kalian akan menangani missing values, duplikat, outlier, tipe data yang salah, dan normalisasi dengan pandas, ditutup praktik membersihkan dataset kotor yang realistis

Di episode 6 kalian mulai eksplorasi data dengan pandas. Sekarang mari hadapi kenyataan yang tidak nyaman: data di dunia nyata itu kotor. Kolom kosong, duplikat, format tanggal yang berantakan, harga bertuliskan "Rp 150.000", dan outlier yang mencurigakan. Statistik yang bagus tidak akan menyelamatkan analisis yang dibangun di atas data kotor — prinsip yang dikenal sebagai garbage in, garbage out.
Episode ini membahas lima masalah data yang paling umum dan cara menanganinya dengan pandas. Tujuannya bukan sekadar hafal fungsi, melainkan memahami kapan harus membersihkan, apa konsekuensi tiap keputusan, dan kapan harus menahan diri — karena cleaning yang berlebihan bisa sama berbahayanya dengan tidak membersihkan sama sekali.
Kita bekerja dengan data pembelian yang sengaja dibuat bermasalah, disimpan sebagai purchase_raw.csv:
id,user_id,product,harga,quantity,tanggal
1,u001,Laptop,"Rp 15.000.000",2,2025-11-01
2,u001,Laptop,"Rp 15.000.000",2,2025-11-01
3,u002,Keyboard,"Rp 500.000",1,01/11/2025
4,,Mouse,"",0,
5,u003,Monitor,"Rp 3.000.000",-1,2025-11-10
6,u004,Keyboard,500000,1,2025-11-12Perhatikan masalahnya: harga bercampur teks dan angka, ada baris tanpa user, harga kosong, quantity negatif, tanggal dengan format berbeda, dan baris ganda. Tugas kalian: jadikan ini tabel yang bisa dianalisis.
import pandas as pd
df = pd.read_csv("purchase_raw.csv")
print(df.dtypes)harga akan terbaca sebagai object (karena ada "Rp 15.000.000") dan tanggal sebagai object (format campuran). Bersihkan angka dulu, baru ubah tipenya:
import re
def bersihkan_harga(v):
if isinstance(v, str):
v = v.replace("Rp", "").replace(".", "").strip()
try:
return float(v)
except ValueError:
return float("nan")
df["harga"] = df["harga"].apply(bersihkan_harga)
df["tanggal"] = pd.to_datetime(df["tanggal"], errors="coerce")
df["harga"] = df["harga"].astype("float")Aturan emas: selalu tetapkan tipe data yang benar sebelum analisis — tanggal jadi datetime, angka jadi float/int, kategori jadi category. Query dan agregasi kemudian berjalan konsisten.
Important
Setiap keputusan cleaning HARUS bisa dijelaskan. Simpan pipeline cleaning sebagai script (bukan hanya diingat), karena stakeholder pasti bertanya "kenapa angka di laporan saya berbeda dari sistem?" Jawaban yang benar dimulai dari data mentah yang jelas prosesnya.
Nilai hilang muncul sebagai NaN. Pilihan penanganannya ada tiga, tergantung konteks:
| Strategi | Kapan dipakai | Risiko |
|---|---|---|
Buang baris (dropna) | Nilai hilang sedikit, baris tak penting | Kehilangan informasi |
Isi nilai (fillna) | Nilai hilang acak, ada estimator wajar | Memasukkan asumsi |
| Biarkan / tandai | Nilai hilang punya arti (misal "tidak pernah order") | Kompleksitas model |
print(df.isna().sum())
df.dropna(subset=["user_id"], inplace=True) # baris tanpa user: buang
df["harga"].fillna(df["harga"].median(), inplace=True) # harga kosong: isi medianIsi dengan median (bukan mean) karena distribusi harga skewed. Dan ingat: untuk kolom kategorikal, menandai "tidak diketahui" kadang lebih jujur daripada menebak.
duplicated() mendeteksi baris yang identik. Di contoh kita, baris 1 dan 2 benar-benar identik — itu duplikat sejati:
df.drop_duplicates(inplace=True)
print(df.duplicated().sum())Hati-hati dengan duplikat semu: dua baris "sama" secara visual tetapi punya kolom pembeda (misal transaction_id). Sebelum drop_duplicates, putuskan subset kolom mana yang mendefinisikan keunikan — misal subset=["user_id", "product", "tanggal"].
Outlier adalah nilai yang jauh dari mayoritas data. Dua pertanyaan wajib sebelum bertindak: apakah ini error data atau fakta nyata? dan apakah ia mengubah kesimpulan kita?
Q1 = df["harga"].quantile(0.25)
Q3 = df["harga"].quantile(0.75)
IQR = Q3 - Q1
batas_bawah = Q1 - 1.5 * IQR
batas_atas = Q3 + 1.5 * IQR
print(df[(df["harga"] < batas_bawah) | (df["harga"] > batas_atas)])Di dataset kita, quantity bernilai negatif jelas error (pasti 1, atau baris yang salah). Tapi harga laptop yang 5x median belum tentu error — bisa jadi itu segmen enterprise. Jangan pernah menghapus outlier otomatis; periksa satu per satu, dokumentasikan keputusan.
df.loc[df["quantity"] < 0, "quantity"] = 1Normalisasi di sini bukan soal statistik, melainkan konsistensi nilai kategorikal: "Jakarta" vs "jakarta" vs "JKT" dianggap tiga nilai berbeda oleh komputer.
df["product"] = df["product"].str.strip().str.title()
print(df["product"].value_counts())Kebiasaan baik: simpan data dictionary sederhana — tabel yang mendokumentasikan tiap kolom, tipe, dan transformasi yang diterapkan. Ini langkah kecil yang membedakan analis profesional dari amatir.
Rangkum semuanya dalam satu fungsi agar bisa dipakai ulang:
def clean_purchase(df):
df = df.copy()
df["harga"] = df["harga"].apply(bersihkan_harga)
df["tanggal"] = pd.to_datetime(df["tanggal"], errors="coerce")
df = df.dropna(subset=["user_id"])
df["harga"] = df["harga"].fillna(df["harga"].median())
df["quantity"] = df["quantity"].clip(lower=1)
df["product"] = df["product"].str.strip().str.title()
df = df.drop_duplicates(subset=["user_id", "product", "tanggal"])
return df
bersih = clean_purchase(pd.read_csv("purchase_raw.csv"))
bersih.to_csv("purchase_clean.csv", index=False)Satu fungsi, dapat diulang, dapat dijelaskan. Ini pola yang akan kalian bawa sampai ke episode 18 (data quality testing).
dropna() tanpa berpikir. Jumlah baris mengecil diam-diam; cek berapa baris yang hilang dan mengapa.NaN dengan 0. Nol bukan berarti "tidak ada data"; ia punya makna sendiri dan merusak rata-rata.Inti yang harus dibawa pulang:
datetime, float), lalu tangani missing, duplikat, outlier, dan konsistensi.Di episode 8 selanjutnya kita akan menganalisis data yang sudah bersih — EDA & feature understanding: analisis univariat, bivariat, korelasi, dan distribusi variabel, plus menyusun laporan EDA singkat. Data kalian sekarang bersih; saatnya membiarkan data berbicara!