Pada chapter sebelumnya kita merapikan isi satu kolom teks, dan hasilnya memang rapi. Masalah konsistensi yang sesungguhnya baru tampak ketika kita berhenti memandang satu kolom dan mulai membandingkan: Tunai di baris ini dengan Cash di baris itu, nama menu di tabel transaksi dengan nama menu di tabel daftar harga. Dua nilai yang bermakna sama tertulis berbeda, dan komputer menaatinya sebagai dua hal yang tidak berhubungan. Pada chapter ini kita memburu varian penulisan dengan value_counts(), menyatukannya dengan rantai metode .str plus kamus pengganti, lalu melihat bagaimana satu huruf kapital bisa membuat penggabungan dua tabel gagal tanpa satu pun pesan galat.

Varian yang Terlihat dari value_counts #

Kolom kategori yang sehat punya sedikit nilai unik: metode pembayaran di kasir Toko Kopi Kita seharusnya hanya tunai dan qris. Cara tercepat menguji dugaan itu adalah menghitung frekuensi setiap nilai. Tabel rekap di bawah memuat delapan pesanan akhir pekan; kolom menu-nya sudah kita rapikan pada chapter Membersihkan Teks , sedangkan kolom metode baru kali ini diperiksa.

python
import pandas as pd

pesanan = pd.DataFrame({
    "pemesan": ["Budi", "Siti", "Dewi", "Rani", "Agus",
                "Farhan", "Tono", "Sari"],
    "menu": ["kopi susu", "es kopi susu", "teh tawar", "americano",
             "matcha latte", "kopi susu", "teh tawar", "es kopi susu"],
    "metode": ["Tunai", "Cash", "tunai", "QRIS",
               "qris", "Cash", "TUNAI", "qris "],
})

print(pesanan)
print()
print("Nilai unik kolom metode:", pesanan["metode"].nunique())
print(pesanan["metode"].value_counts())

Output yang Dihasilkan:

text
  pemesan          menu metode
0    Budi     kopi susu  Tunai
1    Siti  es kopi susu   Cash
2    Dewi     teh tawar  tunai
3    Rani     americano   QRIS
4    Agus  matcha latte   qris
5  Farhan     kopi susu   Cash
6    Tono     teh tawar  TUNAI
7    Sari  es kopi susu  qris 

Nilai unik kolom metode: 7
metode
Cash     2
Tunai    1
tunai    1
QRIS     1
qris     1
TUNAI    1
qris     1
Name: count, dtype: int64

Mari kita telaah baris-barisnya:

  1. Baris 3-10 (pesanan = pd.DataFrame({...})): delapan pesanan dibuat sebagai tabel latihan. Perhatikan kolom metode — sekilas hanya berisi tunai dan QRIS, tetapi penulisannya bervariasi: Tunai, Cash, tunai, QRIS, qris, TUNAI, hingga "qris " yang berakhiran spasi.
  2. Baris 14 (nunique()): hitungan cepat jumlah nilai unik — hasilnya tujuh. Untuk kolom kategori, angka yang lebih besar dari jumlah kategori sebenarnya selalu menjadi bendera peringatan.
  3. Baris 15 (value_counts()): Cash muncul dua kali, sementara keenam nilai lainnya masing-masing sekali. Tujuh label itu sebenarnya hanya mewakili dua metode pembayaran.

Daftar frekuensi yang panjang seperti ini hampir selalu bisa dilacak ke segelintir gejala yang berulang. Tabel berikut memetakan gejala ke alat perbaikannya:

Gejala pada nilaiContohAlat perbaikan
Spasi di tepi teks"qris ".str.strip()
Kapital tak seragam"TUNAI" vs "tunai".str.lower()
Istilah berbeda, makna sama"Cash" vs "tunai".replace() dengan kamus
Singkatan tidak baku"JKT" untuk "Jakarta".replace() dengan kamus

Menyatukan Istilah dengan Kamus Pengganti #

Dua gejala pertama sudah kita selesaikan pada chapter sebelumnya dengan .str.strip() dan .str.lower(). Tetapi hasil keduanya tetap menyisakan cash — kata yang bersih ejaannya namun berbeda istilah dari tunai. Tidak ada metode perapikan otomatis yang tahu bahwa dua kata itu bermakna sama; keputusan itu milik kita, maka kita rumuskan sebagai kamus pengganti dan serahkan pada .replace(). Proses ini disebut normalisasi kategori: menyatukan nilai-nilai yang secara bisnis merujuk kategori yang sama.

python
import pandas as pd

metode = pd.Series(["Tunai", "Cash", "tunai", "QRIS",
                    "qris", "Cash", "TUNAI", "qris "])

kamus = {"cash": "tunai"}

seragam = (metode.str.strip()
                  .str.lower()
                  .replace(kamus))

perbandingan = pd.DataFrame({"asli": metode, "seragam": seragam})
print(perbandingan)
print()
print(seragam.value_counts())

Output yang Dihasilkan:

text
    asli seragam
0  Tunai   tunai
1   Cash   tunai
2  tunai   tunai
3   QRIS    qris
4   qris    qris
5   Cash   tunai
6  TUNAI   tunai
7  qris     qris

tunai    5
qris     3
Name: count, dtype: int64

Mari kita telaah rantai normalisasinya:

  1. Baris 8-10 (metode.str.strip().str.lower().replace(kamus)): tiga langkah berjalan berurutan — tepi dirapikan, kapital diseragamkan menjadi kecil, baru kamus diterapkan. Urutan ini penting: kamus ditulis untuk bentuk akhir ("cash"), jadi ia harus dikerjakan paling akhir.
  2. Baris 6 (kamus = {"cash": "tunai"}): .replace() dengan dictionary mengganti nilai yang cocok seluruhnya — "cash" menjadi tunai, nilai lain dibiarkan. Karena mencocokkan nilai utuh, kamus ini aman dari salah ganti kata sebagian.
  3. Hasil akhir (value_counts()): dari tujuh nilai unik tersisa dua kategori — tunai lima kali dan qris tiga kali. Rekap metode pembayaran kini bisa dihitung tanpa memecah kategori.

Kapital yang Membuat Penggabungan Gagal #

Inkonsistensi paling merugikan muncul ketika dua tabel harus dipertemukan. Kasir mencatat menu sebagaimana diketik kasir — Kopi Susu — sedangkan tim harga menyusun daftar harga dengan huruf kecil — kopi susu. Kita coba menggabungkan keduanya dengan pd.merge untuk membawa harga ke tiap pesanan.

python
import pandas as pd

pesanan = pd.DataFrame({
    "menu": ["Kopi Susu", "Es Kopi Susu", "Teh Tawar", "Americano"],
    "jumlah": [3, 1, 2, 2],
})
daftar_harga = pd.DataFrame({
    "menu": ["kopi susu", "es kopi susu", "teh tawar",
             "americano", "matcha latte"],
    "harga": [24000, 26000, 20000, 22000, 28000],
})

gabung = pd.merge(pesanan, daftar_harga, on="menu", how="left")
print(gabung)
print()
print("Baris tanpa harga:", gabung["harga"].isna().sum(), "dari", len(gabung))

Output yang Dihasilkan:

text
           menu  jumlah  harga
0     Kopi Susu       3    NaN
1  Es Kopi Susu       1    NaN
2     Teh Tawar       2    NaN
3     Americano       2    NaN

Baris tanpa harga: 4 dari 4

Mari kita telaah kegagalannya:

  1. Baris 13 (pd.merge(..., on="menu", how="left")): penggabungan mengacu pada kolom menu di kedua tabel. Nilai Kopi Susu pada tabel kiri dibandingkan dengan nilai kopi susu pada tabel kanan — dan dibandingkan secara persis karakter demi karakter, termasuk besar kecil hurufnya.
  2. Kolom harga yang kosong: tidak ada satu pun kecocokan, sehingga seluruh kolom harga berisi NaN. Empat dari empat pesanan gagal mendapat harga — padahal semua menunya ada di daftar harga.
  3. Baris 16 (isna().sum()): satu baris pemeriksaan ini menyelamatkan kita. Tanpa menghitung kekosongan setelah penggabungan, tabel hasil tampak sah dan analisis berikutnya akan berjalan di atas data yang bolong.

Menyeragamkan Kunci Sebelum Menggabungkan #

Perbaikannya mengikuti pelajaran chapter sebelumnya: seragamkan kolom kunci di kedua sisi sebelum menggabungkan. Karena daftar harga sudah memakai huruf kecil, cukup satu gaya penulisan yang kita tetapkan sebagai kunci — di sini bentuk kecil tanpa spasi tepi.

python
import pandas as pd

pesanan = pd.DataFrame({
    "menu": ["Kopi Susu", "Es Kopi Susu", "Teh Tawar", "Americano"],
    "jumlah": [3, 1, 2, 2],
})
daftar_harga = pd.DataFrame({
    "menu": ["kopi susu", "es kopi susu", "teh tawar",
             "americano", "matcha latte"],
    "harga": [24000, 26000, 20000, 22000, 28000],
})

pesanan["menu"] = pesanan["menu"].str.strip().str.lower()
daftar_harga["menu"] = daftar_harga["menu"].str.strip().str.lower()

gabung = pd.merge(pesanan, daftar_harga, on="menu", how="left")
print(gabung)
print()
print("Baris tanpa harga:", gabung["harga"].isna().sum())
print("Total pendapatan:", (gabung["jumlah"] * gabung["harga"]).sum())

Output yang Dihasilkan:

text
           menu  jumlah  harga
0     kopi susu       3  24000
1  es kopi susu       1  26000
2     teh tawar       2  20000
3     americano       2  22000

Baris tanpa harga: 0
Total pendapatan: 182000

Mari kita telaah perbaikannya:

  1. Baris 13-14 (.str.strip().str.lower()): kedua kolom kunci dinormalkan dengan rantai yang sama — bukan hanya salah satu. Menyeragamkan satu sisi saja tetap membuat kecocokan gagal.
  2. Baris 16 (pd.merge): dengan kunci yang kini identik, keempat pesanan menemukan harganya. Pemeriksaan isna().sum() mengembalikan nol dan total pendapatan 182000 rupiah akhirnya bisa dihitung.
  3. Kolom matcha latte di daftar harga: satu menu tanpa pesanan itu tidak masuk hasil karena how="left" mempertahankan seluruh baris tabel kiri saja. Ragam pilihan how dan aturan kunci penggabungan kita bedah tuntas pada chapter Menggabungkan Dataset .

Rangkuman #

  • nunique() dan value_counts() mendeteksi inkonsistensi kategori: tujuh nilai unik untuk kategori yang seharusnya berisi dua anggota.
  • .str.strip() dan .str.lower() menyeragamkan tepi dan kapital; .replace() dengan kamus menyatukan istilah berbeda bermakna sama seperti Cash menjadi tunai.
  • Kamus pengganti bekerja pada nilai utuh dan diterapkan paling akhir, setelah bentuk teks dirapikan.
  • Perbedaan kapital membuat pd.merge gagal senyap — kolom harga terisi NaN tanpa satu pun pesan galat; hitung isna().sum() setelah setiap penggabungan.
  • Normalisasikan kolom kunci di kedua tabel sebelum menggabungkan, dengan rantai yang sama di kedua sisi.

Nilai kategori dan kunci penggabungan kini seragam. Namun satu jenis kolom masih menyimpan masalah yang tidak terlihat dari value_counts(): tanggal. Teks "15/01/2026" tampak rapi, tetapi ia tidak bisa diurutkan secara kronologis dan bermakna dua tanggal berbeda tergantung siapa yang membacanya. Kita urai kolom waktu itu pada chapter berikutnya, Menangani Format Tanggal .