Bagian 2 ditutup dengan tiga kejanggalan yang sudah dinamai namun belum diobati: satu nama pelanggan yang tercatat dua kali, satu kolom angka yang terbaca sebagai teks, dan satu nilai kota yang berlebih. Pada bagian ini kita membedah ketiganya sampai ke barisnya, memutuskan penanganan masing-masing beserta alasannya, lalu merangkai seluruh keputusan menjadi satu resep pembersihan yang bisa diulang siapa pun. Pembersihan yang baik bukan sekadar membuat data terlihat rapi — setiap keputusan harus bisa dipertanggungjawabkan pada laporan akhir.
Mendiagnosis Tiga Masalah sampai ke Barisnya #
Dugaan dari Bagian 2 kita buktikan satu per satu dengan menyaring baris yang bermasalah:
import pandas as pd
mentah = pd.read_csv("../data/pelanggan.csv")
duplikat = mentah[mentah.duplicated(keep=False)]
print("1) Baris duplikat persis:")
print(duplikat.to_string(index=False))
kosong = mentah[mentah["kunjungan_30hari"] == "-"]
print("\n2) Baris dengan kunjungan \"-\":")
print(kosong.to_string(index=False))
print("\n3) Nilai unik kolom kota:")
print(sorted(mentah["kota"].unique()))Output yang Dihasilkan:
1) Baris duplikat persis:
pelanggan kota kunjungan_30hari belanja_ribu menu_favorit
Budi Santoso Bandung 22 462 kopi susu
Budi Santoso Bandung 22 462 kopi susu
2) Baris dengan kunjungan "-":
pelanggan kota kunjungan_30hari belanja_ribu menu_favorit
Rani Oktaviani Bandung - 320 kopi susu
3) Nilai unik kolom kota:
['Bandung', 'Cimahi', 'Sumedang', 'bandung']Mari kita baca ketiga temuan tersebut:
duplicated(keep=False)memunculkan semua baris yang punya salinan persis — bukan hanya salinannya. Hasilnya: Budi Santoso tercatat dua kali dengan isi yang identik, kemungkinan karena pegawai dua kali menekan simpan saat mendaftarkan namanya. Satu orang, dua baris.- Satu baris memuat
"-"pada kolom kunjungan: Rani Oktaviani. Inilah biang keladi kolomkunjungan_30hariyang terbaca sebagaiobject— satu sel non-angka cukup untuk mengubah tipe seluruh kolom. Bu Rina mengenali kasusnya: kartu member Rani baru dibuat minggu lalu, sehingga rekap kunjungannya belum terisi. - Daftar kota memuat
bandungdengan huruf kecil di sampingBandung. Untuk komputer keduanya adalah dua nilai berbeda; agregasi per kota akan membelah Bandung menjadi dua.
Keputusan Penanganan dan Alasannya #
Ketiga masalah itu menuntut tiga keputusan yang tidak sama derajatnya:
| Masalah | Keputusan | Alasan |
|---|---|---|
| Budi Santoso tercatat dua kali | Buang salinannya, simpan satu | Dua baris identik berarti satu orang dihitung dua kali pada setiap rata-rata |
Kunjungan Rani Oktaviani "-" | Buang barisnya seluruhnya | Belanja Rp 320.000 tercatat, tetapi jumlah kunjungan tidak; tanpa kunjungan, fitur belanja per kunjungan tidak bisa dihitung secara adil |
bandung vs Bandung | Seragamkan ke bentuk baku Bandung | Nilai sebenarnya benar; hanya penulisannya yang perlu dirapikan |
Keputusan pada baris kedua paling berat konsekuensinya, jadi perlu dibahas terbuka. Alternatifnya adalah mengisi kunjungan Rani dengan rata-rata seluruh pelanggan — tetapi itu berarti mengarang angka untuk laporan yang akan dipakai mengambil keputusan bisnis. Satu baris dari 32 tidak akan mengubah besar gambar analisis, sedangkan angka karangan selalu berisiko menyesatkan. Kita membuang baris itu dengan sadar dan mencatatnya sebagai keterbatasan di laporan akhir; topik nilai kosong dan pilihan penanganannya dibahas lebih dalam pada panduan missing data pandas .
Resep Bersih yang Dapat Diulang #
Seluruh keputusan di atas kita rangkai menjadi satu fungsi muat_bersih. Fungsi ini adalah resep resmi proyek: bagian-bagian berikutnya menyalin resep yang sama setiap kali membaca data, sehingga setiap angka dihitung dari data bersih yang identik — dan src/segmentasi.py pun memakai langkah-langkah yang sama di fungsi bersihkan():
import pandas as pd
def muat_bersih(path):
"""Muat data mentah pelanggan dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path)
df = df.drop_duplicates().reset_index(drop=True)
df["kunjungan_30hari"] = pd.to_numeric(df["kunjungan_30hari"], errors="coerce")
df = df.dropna(subset=["kunjungan_30hari"]).reset_index(drop=True)
df["kota"] = df["kota"].str.strip().str.title()
df["kunjungan_30hari"] = df["kunjungan_30hari"].astype(int)
return df
bersih = muat_bersih("../data/pelanggan.csv")
print("Ukuran data bersih :", bersih.shape)
print("Duplikat tersisa :", bersih.duplicated().sum())
print("Kunjungan kosong :", bersih["kunjungan_30hari"].isna().sum())
print("Tipe kolom kunjungan :", bersih["kunjungan_30hari"].dtype)
print("Kota unik :", sorted(bersih["kota"].unique()))
dewi = bersih.loc[bersih["pelanggan"] == "Dewi Lestari", "kota"].iloc[0]
print("Kota Dewi Lestari :", dewi)Output yang Dihasilkan:
Ukuran data bersih : (30, 5)
Duplikat tersisa : 0
Kunjungan kosong : 0
Tipe kolom kunjungan : int64
Kota unik : ['Bandung', 'Cimahi', 'Sumedang']
Kota Dewi Lestari : BandungMari kita bedah resepnya baris demi baris:
drop_duplicates(): salinan Budi Santoso hilang; tersisa satu baris per pelanggan.reset_index(drop=True)merapikan penomoran setelah baris dibuang.pd.to_numeric(..., errors="coerce"): tanda"-"berubah menjadiNaN— bentuk resmi nilai kosong — dan kolom kini berupa angka atau kosong, tidak lagi teks.dropna(subset=["kunjungan_30hari"]): baris Rani Oktaviani dibuang sesuai keputusan kita, lalu indeksnya dirapikan.str.strip().str.title(): spasi di ujung dipangkas lalu tiap kata dimulai huruf kapital —bandungmenjadiBandung, danBandungtetapBandung.astype(int): setelah tak ada nilai kosong, kolom kunjungan menjadi bilangan bulat yang sesungguhnya — siap untuk perhitungan apa pun.
Hasil verifikasi menunjukkan seluruh masalah selesai: 30 baris bersih, tanpa duplikat, tanpa nilai kosong, tipe kolom sudah int64, dan kota tinggal tiga nilai yang benar. Data bersih kini menunggu dibaca ceritanya — mulai dari statistik deskriptif sampai dugaan struktur kelompoknya — pada Bagian 4: Eksplorasi Data
.