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:

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

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

  1. 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.
  2. Satu baris memuat "-" pada kolom kunjungan: Rani Oktaviani. Inilah biang keladi kolom kunjungan_30hari yang terbaca sebagai object — 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.
  3. Daftar kota memuat bandung dengan huruf kecil di samping Bandung. 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:

MasalahKeputusanAlasan
Budi Santoso tercatat dua kaliBuang salinannya, simpan satuDua baris identik berarti satu orang dihitung dua kali pada setiap rata-rata
Kunjungan Rani Oktaviani "-"Buang barisnya seluruhnyaBelanja Rp 320.000 tercatat, tetapi jumlah kunjungan tidak; tanpa kunjungan, fitur belanja per kunjungan tidak bisa dihitung secara adil
bandung vs BandungSeragamkan ke bentuk baku BandungNilai 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():

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

text
Ukuran data bersih   : (30, 5)
Duplikat tersisa     : 0
Kunjungan kosong     : 0
Tipe kolom kunjungan : int64
Kota unik            : ['Bandung', 'Cimahi', 'Sumedang']
Kota Dewi Lestari    : Bandung

Mari kita bedah resepnya baris demi baris:

  1. drop_duplicates(): salinan Budi Santoso hilang; tersisa satu baris per pelanggan. reset_index(drop=True) merapikan penomoran setelah baris dibuang.
  2. pd.to_numeric(..., errors="coerce"): tanda "-" berubah menjadi NaN — bentuk resmi nilai kosong — dan kolom kini berupa angka atau kosong, tidak lagi teks.
  3. dropna(subset=["kunjungan_30hari"]): baris Rani Oktaviani dibuang sesuai keputusan kita, lalu indeksnya dirapikan.
  4. str.strip().str.title(): spasi di ujung dipangkas lalu tiap kata dimulai huruf kapital — bandung menjadi Bandung, dan Bandung tetap Bandung.
  5. 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 .