Merge adalah operasi pandas yang paling sering menghasilkan “data benar tapi salah jumlah”. Empat pelanggan digabungkan dengan lima pesanan bisa menghasilkan enam baris, dan pandas tidak menganggap itu kesalahan — hasil itu memang definisi dari join yang diminta. Chapter ini memberi kita alat pemeriksaan yang sistematis: membandingkan jumlah baris sebelum dan sesudah merge, menemukan kunci join yang duplikat, memetakan asal setiap baris dengan indicator=True, dan mengeksekusi bentuk relasi dengan validate=.

Kenapa Jumlah Baris Bisa Berubah #

Aturan dasarnya sederhana: satu baris kiri dipasangkan dengan setiap baris kanan yang kuncinya sama. Jika satu pelanggan punya tiga pesanan, hasil merge memuat tiga baris untuk pelanggan tersebut. Perilaku ini benar menurut definisi join, tetapi jarang menjadi keinginan saat kita hanya ingin membawa atribut tambahan. Karena itu pemeriksaan pertama saat hasil merge tidak sesuai ekspektasi selalu berupa perbandingan angka sederhana, bukan pembacaan kode ulang. Script berikut memakai data pelanggan dan pesanan Toko Maju Jaya untuk mendemokan semua alat pemeriksaannya.

python
import pandas as pd

pelanggan = pd.DataFrame(
    {
        "id_pelanggan": [1, 2, 3, 4],
        "nama": ["Budi", "Siti", "Dewi", "Rian"],
    }
)
pesanan = pd.DataFrame(
    {
        "id_pelanggan": [1, 1, 2, 2, 9],
        "kode": ["TRX-001", "TRX-002", "TRX-003", "TRX-004", "TRX-005"],
        "total": [50_000, 30_000, 20_000, 45_000, 25_000],
    }
)

print("--- jumlah baris sebelum dan sesudah merge ---")
print("baris pelanggan :", len(pelanggan))
print("baris pesanan   :", len(pesanan))
gabungan = pelanggan.merge(pesanan, on="id_pelanggan", how="left")
print("baris hasil     :", len(gabungan))
print()

print("--- deteksi kunci duplikat di kedua sisi ---")
print("pesanan per id:", pesanan["id_pelanggan"].value_counts().sort_index().to_dict())
print("pelanggan unik:", pelanggan["id_pelanggan"].is_unique)
print()

print("--- indicator=True memetakan asal baris ---")
laporan = pelanggan.merge(pesanan, on="id_pelanggan", how="outer", indicator=True)
print("kategori _merge :", laporan["_merge"].value_counts().to_dict())
print("left_only       :", laporan.loc[laporan["_merge"] == "left_only", "nama"].tolist())
print("right_only      :", laporan.loc[laporan["_merge"] == "right_only", "kode"].tolist())
print()

print("--- validate= menjaga bentuk relasi ---")
aman = pesanan.merge(pelanggan, on="id_pelanggan", validate="many_to_one")
print("many_to_one -> ok, baris:", len(aman))
try:
    pelanggan.merge(pesanan, on="id_pelanggan", validate="one_to_one")
except pd.errors.MergeError as exc:
    print("one_to_one  ->", type(exc).__name__)
    print("pesan       :", str(exc).splitlines()[0])
print()

print("--- kolom sama-sama bernama stok ---")
stok_kasir = pd.DataFrame({"kode_barang": ["B01"], "stok": [10]})
stok_gudang = pd.DataFrame({"kode_barang": ["B01"], "stok": [99]})
gabung = stok_kasir.merge(stok_gudang, on="kode_barang")
print("suffix default:", gabung.columns.tolist())
try:
    stok_kasir.merge(stok_gudang, on="kode_barang", suffixes=("", ""))
except ValueError as exc:
    print("suffix kosong ->", str(exc))

Output yang Dihasilkan:

text
--- jumlah baris sebelum dan sesudah merge ---
baris pelanggan : 4
baris pesanan   : 5
baris hasil     : 6

--- deteksi kunci duplikat di kedua sisi ---
pesanan per id: {1: 2, 2: 2, 9: 1}
pelanggan unik: True

--- indicator=True memetakan asal baris ---
kategori _merge : {'both': 4, 'left_only': 2, 'right_only': 1}
left_only       : ['Dewi', 'Rian']
right_only      : ['TRX-005']

--- validate= menjaga bentuk relasi ---
many_to_one -> ok, baris: 4
one_to_one  -> MergeError
pesan       : Merge keys are not unique in right dataset; not a one-to-one merge

--- kolom sama-sama bernama stok ---
suffix default: ['kode_barang', 'stok_x', 'stok_y']
suffix kosong -> columns overlap but no suffix specified: Index(['stok'], dtype='str')

Mari kita bedah bagian pentingnya:

  1. Perbandingan len() tiga arah: pelanggan punya 4 baris, pesanan 5 baris, hasil merge 6 baris. Selisih len(gabungan) - len(pelanggan) = 2 langsung memberi tanda bahwa dua pelanggan muncul lebih dari satu kali di sisi pesanan. Angka ini sebaiknya dihitung setiap kali kita menulis merge baru.
  2. value_counts() pada kunci: pesanan per id adalah {1: 2, 2: 2, 9: 1}, jadi kunci 1 dan 2 duplikat di sisi kanan. Di sisi lain pelanggan["id_pelanggan"].is_unique bernilai True. Kombinasi “kiri unik, kanan duplikat” menjelaskan dari mana dua baris tambahan berasal.
  3. indicator=True: kolom _merge mengelompokkan setiap baris hasil outer join menjadi both, left_only, atau right_only. Hasilnya {'both': 4, 'left_only': 2, 'right_only': 1} — pelanggan Dewi dan Rian tidak punya pesanan, sedangkan pesanan TRX-005 (id 9) tidak punya pelanggan. Kunci id 9 seperti ini biasanya berasal dari data uji yang tertinggal dan layak diperiksa ke sumbernya.
  4. validate="many_to_one": relasi “banyak pesanan per satu pelanggan” cocok dengan data, sehingga merge diterima. Kalau struktur data berubah suatu hari (misalnya pelanggan ikut duplikat), merge yang sama akan langsung ditolak — masalah terdeteksi di baris merge, bukan saat laporan sudah jadi.
  5. validate="one_to_one": muncul pd.errors.MergeError dengan pesan pertama Merge keys are not unique in right dataset; not a one-to-one merge. Pesan ini menyebut sisi mana yang duplikat, sehingga pemeriksaan kunci langsung terarah.
  6. Kolom dengan nama sama: dua DataFrame yang sama-sama punya kolom stok otomatis diberi suffix stok_x dan stok_y. Suffix bawaan seperti ini mudah luput dibaca. Memberi suffixes=("", "") memicu error columns overlap but no suffix specified — pola yang praktis untuk memastikan kita tidak pernah memakai _x/_y tanpa sadar; sebutkan suffix yang bermakna, misalnya suffixes=("_kasir", "_gudang").

Alat Pemeriksaan Merge Sekali Pandang #

AlatKegunaan
len() sebelum vs sesudahMendeteksi penggandaan baris atau baris yang tidak ikut
value_counts() pada kunciMenemukan kunci duplikat di tiap sisi
is_uniqueMenegaskan apakah kunci layak jadi satu-satu
indicator=TrueMemetakan baris ke both / left_only / right_only
validate=Menolak merge yang bentuk relasinya berubah
suffixes=Menamai kolom yang bentrok dengan bermakna

Begitu jumlah baris terkendali, sumber keanehan berikutnya sering berada di tipe data: kunci join yang tampak sama tapi dtype-nya berbeda, atau kolom angka yang berubah bentuk saat dibaca. Topik itu kita bahas tuntas pada chapter Debugging dtype.

Referensi resmi untuk chapter ini: Merge, join, concatenate and compare , pandas.DataFrame.merge , dan pandas.errors.MergeError .