Ketika sebuah test DataFrame gagal, pertanyaan kita sebenarnya sederhana: apa persisnya yang berbeda? DataFrame.equals() hanya menjawab ya atau tidak, sedangkan pandas.testing.assert_frame_equal() menjawab dengan presisi — baris mana, kolom mana, dtype apa, sampai nilai pertama yang tidak cocok. Utilitas ini adalah standar perbandingan DataFrame di ekosistem pytest dan punya sejumlah opsi yang menentukan kapan dua DataFrame dianggap sama. Chapter ini membedah perilaku bawaannya satu per satu, lengkap dengan kasus toleransi float, dtype, urutan index, dan NaN.
Perilaku Bawaan yang Perlu Dipahami #
assert_frame_equal(kiri, kanan) secara bawaan memeriksa bentuk, urutan index, nama kolom, dtype setiap kolom, dan nilainya. Untuk kolom float, perbandingan nilai memakai toleransi: parameter rtol bawaan 1e-5 (perbedaan relatif hingga 0,001 persen diterima) dan atol bawaan 1e-8. Kombinasi ini masuk akal untuk hasil pembagian atau agregasi float yang jarang persis identik, tetapi bisa menyembunyikan perbedaan kecil yang justru ingin kita tangkap. Script berikut memperagakan setiap perilaku bawaan dan opsi yang mengubahnya — semua dalam satu skrip yang bisa dijalankan berulang.
import pandas as pd
from pandas.testing import assert_frame_equal, assert_index_equal, assert_series_equal
def coba(label: str, aksi) -> None:
"""Jalankan satu perbandingan dan laporkan hasilnya dalam satu baris."""
try:
aksi()
except AssertionError as exc:
print(f"{label}: GAGAL -> {str(exc).splitlines()[0]}")
else:
print(f"{label}: lolos")
kiri = pd.DataFrame({"omzet": [1_000.0, 2_000.0]})
selisih_kecil = pd.DataFrame({"omzet": [1_000.00001, 2_000.0]})
selisih_besar = pd.DataFrame({"omzet": [1_100.0, 2_000.0]})
print("--- toleransi bawaan (rtol=1e-5, atol=1e-8) ---")
coba("selisih 0.00001 ", lambda: assert_frame_equal(kiri, selisih_kecil))
coba("selisih 100 (10 persen)", lambda: assert_frame_equal(kiri, selisih_besar))
coba("check_exact=True ", lambda: assert_frame_equal(kiri, selisih_kecil, check_exact=True))
print()
print("--- pemeriksaan dtype ---")
utuh = pd.DataFrame({"omzet": [1_000, 2_000]})
pecahan = pd.DataFrame({"omzet": [1_000.0, 2_000.0]})
coba("int64 vs float64 ", lambda: assert_frame_equal(utuh, pecahan))
coba("check_dtype=False ", lambda: assert_frame_equal(utuh, pecahan, check_dtype=False))
print()
print("--- urutan index ---")
acak = pd.DataFrame({"omzet": [2_000.0, 1_000.0]}, index=[1, 0])
coba("urutan index beda ", lambda: assert_frame_equal(kiri, acak))
coba("check_like=True ", lambda: assert_frame_equal(kiri, acak, check_like=True))
print()
print("--- NaN dianggap sama jika posisinya sama ---")
dengan_nan = pd.DataFrame({"omzet": [1_000.0, None]})
coba("NaN di posisi sama ", lambda: assert_frame_equal(dengan_nan, dengan_nan.copy()))
coba("NaN vs angka ", lambda: assert_frame_equal(dengan_nan, kiri))
print()
print("--- pesan kegagalan lengkap untuk satu kasus ---")
try:
assert_frame_equal(kiri, selisih_besar)
except AssertionError as exc:
for baris in str(exc).splitlines():
print(f" {baris}")
print()
print("--- varian Series dan Index ---")
coba("assert_series_equal ", lambda: assert_series_equal(kiri["omzet"], selisih_besar["omzet"]))
coba("assert_index_equal ", lambda: assert_index_equal(kiri.index, acak.index))
coba("assert_index_equal ok ", lambda: assert_index_equal(kiri.index, pd.Index([0, 1])))Output yang Dihasilkan:
--- toleransi bawaan (rtol=1e-5, atol=1e-8) ---
selisih 0.00001 : lolos
selisih 100 (10 persen): GAGAL -> DataFrame.iloc[:, 0] (column name="omzet") are different
check_exact=True : GAGAL -> DataFrame.iloc[:, 0] (column name="omzet") are different
--- pemeriksaan dtype ---
int64 vs float64 : GAGAL -> Attributes of DataFrame.iloc[:, 0] (column name="omzet") are different
check_dtype=False : lolos
--- urutan index ---
urutan index beda : GAGAL -> DataFrame.index are different
check_like=True : lolos
--- NaN dianggap sama jika posisinya sama ---
NaN di posisi sama : lolos
NaN vs angka : GAGAL -> DataFrame.iloc[:, 0] (column name="omzet") are different
--- pesan kegagalan lengkap untuk satu kasus ---
DataFrame.iloc[:, 0] (column name="omzet") are different
DataFrame.iloc[:, 0] (column name="omzet") values are different (50.0 %)
[index]: [0, 1]
[left]: [1000.0, 2000.0]
[right]: [1100.0, 2000.0]
At positional index 0, first diff: 1000.0 != 1100.0
--- varian Series dan Index ---
assert_series_equal : GAGAL -> Series are different
assert_index_equal : GAGAL -> Index are different
assert_index_equal ok : lolosMari kita bedah bagian pentingnya:
selisih 0.00001: kolomomzetberbeda 0,00001 pada nilai 1.000 — jauh di dalam toleransirtol=1e-5, sehingga perbandingan lolos. Perbandingan float tanpa toleransi biasanya menghasilkan test yang rapuh terhadap urutan operasi aritmetika.selisih 100 (10 persen): perbedaan melewati toleransi danAssertionErrorterlempar. Baris pertama pesannya menyebut lokasi secara presisi:DataFrame.iloc[:, 0] (column name="omzet") are different.check_exact=True: selisih kecil 0,00001 yang tadinya lolos kini gagal. Mode ini tepat untuk dtype yang harus persis — integer, string, tanggal — dan untuk kasus saat kita tahu hasilnya memang harus identik bit demi bit.int64 vs float64: nilai sama tetapi dtype berbeda, danassert_frame_equaltetap menolaknya karenacheck_dtypebawaanTrue. Menambahcheck_dtype=Falsemembuat perbandingan lolos — opsi yang berguna saat dtype hasil konstruksi memang berbeda dari hasil pembacaan file.urutan index beda: dua DataFrame dengan baris yang sama tetapi index 0-1 versus 1-0 ditolak, karena urutan index adalah bagian dari identitas.check_like=Truemenyuruh perbandingan menyamakan urutan index dan kolom terlebih dahulu, sehingga lolos.NaN di posisi sama: duaNaNpada posisi yang sama dianggap sama oleh utilitas ini — berbeda dari operator==yang menganggapNaNtidak sama dengan apa pun. SebaliknyaNaNdibandingkan dengan angka tetap gagal.- Pesan kegagalan lengkap: output mencetak satu pesan utuh yang memuat persentase sel yang berbeda (
50.0 %), nilai[left]dan[right], hingga barisAt positional index 0, first diff: 1000.0 != 1100.0. Pesan ini dirancang agar kita tidak perlu mencetak kedua DataFrame secara manual. assert_series_equal()danassert_index_equal(): saudara satu keluarga dengan aturan yang sama, untuk membandingkan satu kolom atau satu index saja. Ketiganya dipanggil tanpa pytest pun berjalan, karena hanya berada di modulpandas.testing.
Peta Opsi yang Paling Sering Dipakai #
| Opsi | Bawaan | Efek |
|---|---|---|
check_dtype | True | Dtype kolom harus sama; False melewatkan pemeriksaan ini |
check_exact | False untuk float, True untuk dtype eksak | Memaksa perbandingan nilai tanpa toleransi |
rtol / atol | 1e-5 / 1e-8 | Toleransi relatif dan absolut untuk nilai float |
check_like | False | Menyamakan urutan index dan kolom sebelum membandingkan |
check_names | True | Nama index dan kolom ikut diperiksa |
Membandingkan dua DataFrame adalah alat pemeriksaan di ujung alur kerja; bagian awalnya adalah memastikan data yang masuk memang berbentuk yang dijanjikan. Chapter berikutnya, Validasi Schema Data, membangun pemeriksaan kolom, dtype, dan aturan isi terhadap kontrak yang disepakati sebelum data diproses lebih jauh.
Referensi resmi untuk chapter ini: pandas.testing.assert_frame_equal , pandas.testing.assert_series_equal , dan pandas.testing.assert_index_equal .