Nilai hilang setelah ditangani, satu masalah klasik masih menunggu: baris yang terekam dua kali. Duplikat jarang terlihat di cuplikan data, tetapi dampaknya nyata — pendapatan menggelembung, jumlah pelanggan dihitung ganda, dan model belajar dari contoh yang sama berulang. Pada chapter ini kita membedah duplikat pada data gabungan kasir dan rekap manual Toko Kopi Kita: menandainya dengan duplicated(), mengendalikan parameter keep, menghapusnya dengan drop_duplicates(), dan yang terpenting — membedakan duplikat sungguhan dari transaksi sah yang kebetulan mirip.
Ketika Dua Sistem Mencatat Hal yang Sama #
Toko Kopi Kita menggabungkan dua catatan: aplikasi kasir sebagai sumber utama dan rekap manual sebagai cadangan. Proses penggabungan yang terburu-buru membuat sebagian transaksi terekam dua kali. Data hasilnya memuat dua jenis duplikat yang perlu dibedakan sejak awal.
| Jenis | Ciri | Contoh pada data kita |
|---|---|---|
| Duplikat penuh | Seluruh kolom identik dengan baris lain | T005 tercatat dua kali persis sama |
| Duplikat parsial | Sebagian kolom sama, sebagian berbeda | T008 tercatat dua kali dengan jumlah berbeda — kasir mengoreksi catatan |
Duplikat penuh bisa dihapus begitu saja. Duplikat parsial menuntut keputusan: baris mana yang benar? Tanpa kunci pengenal seperti kolom id, dua transaksi kopi susu dari pelanggan yang sama di hari yang sama bisa dianggap duplikat padahal keduanya sah — dan itu bukan pembersihan data, melainkan penghapusan transaksi nyata.
Menandai Baris Duplikat dengan duplicated #
Fungsi duplicated() mengembalikan nilai True untuk setiap baris yang sama persis dengan baris sebelumnya. Dengan keep=False, semua baris yang terlibat penggandaan ditandai — bukan hanya salinannya — sehingga kita bisa memeriksa keduanya berdampingan.
import pandas as pd
baris = [
("T001", "2026-01-21", "kopi susu", 2, "Budi"),
("T002", "2026-01-21", "teh tawar", 1, "Siti"),
("T003", "2026-01-22", "americano", 2, "Dewi"),
("T004", "2026-01-22", "matcha latte", 3, "Rani"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T006", "2026-01-23", "teh tawar", 2, "Budi"),
("T007", "2026-01-24", "americano", 1, "Siti"),
("T008", "2026-01-24", "kopi susu", 2, "Dewi"),
("T008", "2026-01-24", "kopi susu", 3, "Dewi"),
("T009", "2026-01-25", "matcha latte", 2, "Agus"),
]
transaksi = pd.DataFrame(baris, columns=["id", "tanggal", "produk", "jumlah", "pelanggan"])
print(transaksi)
print()
print("Baris duplikat penuh:", transaksi.duplicated().sum())
print()
print("Baris yang terlibat duplikat penuh:")
print(transaksi[transaksi.duplicated(keep=False)])Output yang Dihasilkan:
id tanggal produk jumlah pelanggan
0 T001 2026-01-21 kopi susu 2 Budi
1 T002 2026-01-21 teh tawar 1 Siti
2 T003 2026-01-22 americano 2 Dewi
3 T004 2026-01-22 matcha latte 3 Rani
4 T005 2026-01-23 kopi susu 1 Agus
5 T005 2026-01-23 kopi susu 1 Agus
6 T006 2026-01-23 teh tawar 2 Budi
7 T007 2026-01-24 americano 1 Siti
8 T008 2026-01-24 kopi susu 2 Dewi
9 T008 2026-01-24 kopi susu 3 Dewi
10 T009 2026-01-25 matcha latte 2 Agus
Baris duplikat penuh: 1
Baris yang terlibat duplikat penuh:
id tanggal produk jumlah pelanggan
4 T005 2026-01-23 kopi susu 1 Agus
5 T005 2026-01-23 kopi susu 1 AgusMari kita telaah hasilnya:
- Baris 20 (
duplicated().sum()): satu baris dinyatakan duplikat — salinan keduaT005. Baris pertamanya tidak dihitung karena secara bawaanduplicated()menandai baris setelah kemunculan pertama. - Baris 22-23 (
duplicated(keep=False)): saringan memperlihatkan kedua barisT005sekaligus — yang asli dan salinannya — supaya kita bisa memastikan keduanya memang identik sebelum menghapus. - Pasangan
T008tidak tertangkap: baris 9 dan 10 berbagiidtetapi berbeda di kolomjumlah(2 lawan 3), sehingga keduanya bukan duplikat penuh. Duplikat parsial semacam ini butuh perlakuan khusus di bagian berikutnya.
Mengatur Baris Asli dengan Parameter keep #
Parameter keep menentukan kemunculan mana yang dianggap “asli”. Tiga pilihannya mengubah hitungan duplikat secara langsung — dan pilihan yang keliru bisa membuat kita membuang baris yang justru benar.
import pandas as pd
baris = [
("T001", "2026-01-21", "kopi susu", 2, "Budi"),
("T002", "2026-01-21", "teh tawar", 1, "Siti"),
("T003", "2026-01-22", "americano", 2, "Dewi"),
("T004", "2026-01-22", "matcha latte", 3, "Rani"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T006", "2026-01-23", "teh tawar", 2, "Budi"),
("T007", "2026-01-24", "americano", 1, "Siti"),
("T008", "2026-01-24", "kopi susu", 2, "Dewi"),
("T008", "2026-01-24", "kopi susu", 3, "Dewi"),
("T009", "2026-01-25", "matcha latte", 2, "Agus"),
]
transaksi = pd.DataFrame(baris, columns=["id", "tanggal", "produk", "jumlah", "pelanggan"])
print('keep="first":', transaksi.duplicated(keep="first").sum(), "baris")
print('keep="last" :', transaksi.duplicated(keep="last").sum(), "baris")
print("keep=False :", transaksi.duplicated(keep=False).sum(), "baris")Output yang Dihasilkan:
keep="first": 1 baris
keep="last" : 1 baris
keep=False : 2 barisKetiganya menghitung pasangan T005 yang sama, tetapi menandai baris yang berbeda:
Nilai keep | Baris yang dianggap asli | Baris yang ditandai |
|---|---|---|
"first" (bawaan) | Kemunculan pertama | Salinan kedua dan seterusnya |
"last" | Kemunculan terakhir | Semua salinan sebelumnya |
False | Tidak ada | Seluruh baris yang terlibat, termasuk yang pertama |
Untuk T005 yang salinannya identik, ketiga pilihan menghasilkan data akhir yang sama. Pilihan baru berdampak ketika salinannya berbeda — seperti T008 yang jumlahnya dikoreksi dari 2 menjadi 3. Di sana, keep="last" berarti “percayai catatan terbaru”, dan itu tepat karena kasir memang mengoreksi catatan lamanya.
Menghapus dengan drop_duplicates #
Penghapusan memakai drop_duplicates(). Tanpa parameter, fungsi ini hanya membuang duplikat penuh; parameter subset mempersempit perbandingan ke kolom kunci — di data kita, id — sehingga duplikat parsial ikut tertangani.
import pandas as pd
baris = [
("T001", "2026-01-21", "kopi susu", 2, "Budi"),
("T002", "2026-01-21", "teh tawar", 1, "Siti"),
("T003", "2026-01-22", "americano", 2, "Dewi"),
("T004", "2026-01-22", "matcha latte", 3, "Rani"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T006", "2026-01-23", "teh tawar", 2, "Budi"),
("T007", "2026-01-24", "americano", 1, "Siti"),
("T008", "2026-01-24", "kopi susu", 2, "Dewi"),
("T008", "2026-01-24", "kopi susu", 3, "Dewi"),
("T009", "2026-01-25", "matcha latte", 2, "Agus"),
]
transaksi = pd.DataFrame(baris, columns=["id", "tanggal", "produk", "jumlah", "pelanggan"])
tanpa_duplikat_penuh = transaksi.drop_duplicates()
print("Dedup penuh :", transaksi.shape, "->", tanpa_duplikat_penuh.shape)
per_id = transaksi.drop_duplicates(subset=["id"], keep="last")
print("Dedup per id :", transaksi.shape, "->", per_id.shape)
print()
print(per_id)Output yang Dihasilkan:
Dedup penuh : (11, 5) -> (10, 5)
Dedup per id : (11, 5) -> (9, 5)
id tanggal produk jumlah pelanggan
0 T001 2026-01-21 kopi susu 2 Budi
1 T002 2026-01-21 teh tawar 1 Siti
2 T003 2026-01-22 americano 2 Dewi
3 T004 2026-01-22 matcha latte 3 Rani
5 T005 2026-01-23 kopi susu 1 Agus
6 T006 2026-01-23 teh tawar 2 Budi
7 T007 2026-01-24 americano 1 Siti
9 T008 2026-01-24 kopi susu 3 Dewi
10 T009 2026-01-25 matcha latte 2 AgusMari kita telaah dua hasil yang berbeda itu:
- Baris 20 (
drop_duplicates()): hanya satu baris yang terbuang — salinan keduaT005. Sepuluh baris tersisa, tetapiT008masih ada dua kali karena barisnya tidak identik. - Baris 23 (
drop_duplicates(subset=["id"], keep="last")): perbandingan dibatasi ke kolomid, sehingga kedua barisT008dianggap sama dan hanya catatan terakhir yang dipertahankan — yang berjumlah 3 cangkir. Sembilan baris tersisa: satu baris per transaksi. - Cetakan
per_id: hasil akhirnya rapi —T005tunggal,T008tunggal dengan koreksi terbaru, urutan lain tetap utuh.
Berapa Besar Dampaknya ke Angka #
Pembersihan tanpa pengukuran hanyalah keyakinan. Kita hitung dampak tiap strategi terhadap tiga angka yang dipakai manajemen toko: jumlah transaksi, total cangkir terjual, dan pendapatan. Harga produk diambil dari daftar harga tetap toko.
import pandas as pd
baris = [
("T001", "2026-01-21", "kopi susu", 2, "Budi"),
("T002", "2026-01-21", "teh tawar", 1, "Siti"),
("T003", "2026-01-22", "americano", 2, "Dewi"),
("T004", "2026-01-22", "matcha latte", 3, "Rani"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T005", "2026-01-23", "kopi susu", 1, "Agus"),
("T006", "2026-01-23", "teh tawar", 2, "Budi"),
("T007", "2026-01-24", "americano", 1, "Siti"),
("T008", "2026-01-24", "kopi susu", 2, "Dewi"),
("T008", "2026-01-24", "kopi susu", 3, "Dewi"),
("T009", "2026-01-25", "matcha latte", 2, "Agus"),
]
transaksi = pd.DataFrame(baris, columns=["id", "tanggal", "produk", "jumlah", "pelanggan"])
harga_produk = {"kopi susu": 24000, "teh tawar": 20000,
"americano": 22000, "matcha latte": 28000}
def ringkas(df, label):
pendapatan = (df["jumlah"] * df["produk"].map(harga_produk)).sum()
tebal = f"Rp{int(pendapatan):,}".replace(",", ".")
print(f"{label}: {len(df)} transaksi, {df['jumlah'].sum()} cangkir, {tebal}")
ringkas(transaksi, "Sebelum dedup ")
ringkas(transaksi.drop_duplicates(), "Dedup penuh ")
ringkas(transaksi.drop_duplicates(subset=["id"], keep="last"), "Dedup per id (akhir)")Output yang Dihasilkan:
Sebelum dedup : 11 transaksi, 20 cangkir, Rp482.000
Dedup penuh : 10 transaksi, 19 cangkir, Rp458.000
Dedup per id (akhir): 9 transaksi, 17 cangkir, Rp410.000Tiga baris ringkasan tersebut bercerita banyak. Data mentah melaporkan 11 transaksi dan pendapatan Rp482.000 — padahal satu transaksi tercatat dua kali dan satu catatan T008 yang usang ikut terhitung. Dedup penuh memperbaiki sebagian (Rp458.000), dan dedup per id menghasilkan angka yang benar: 9 transaksi, 17 cangkir, Rp410.000. Selisih Rp72.000 adalah pendapatan semu yang tanpa pembersihan akan masuk ke laporan manajemen.
Rangkuman #
duplicated()menandai baris identik dengan baris sebelumnya;keep=Falsemenampilkan semua baris yang terlibat untuk diperiksa sebelum dihapus.- Parameter
keepmengatur kemunculan mana yang dianggap asli —"first"bawaan,"last"untuk memercayai catatan terbaru. drop_duplicates()tanpa parameter hanya menangani duplikat penuh;subsetpada kolom kunci sepertiidmenangani duplikat parsial.subsetyang longgar dapat menghapus transaksi sah yang mirip; batasi pada kolom yang benar-benar unik.- Ukur dampaknya: hitung ulang metrik utama sebelum dan sesudah dedup — perbedaannya adalah kesalahan yang berhasil dicegah.
Dengan duplikat bersih, baris-baris data kini mewakili kejadian yang sesungguhnya. Namun bila kita mencoba menghitung pendapatan pada berkas penjualan_kotor.csv dari chapter pertama, penjumlahannya justru merekatkan teks alih-alih menjumlahkan angka — tanda tipe kolomnya belum benar. Kita perbaiki berbagai jenis tipe yang salah itu pada chapter berikutnya, Memperbaiki Tipe Data
.