Operasi pandas jarang gagal keras karena masalah index — justru itu yang membuatnya licik. Dua Series dengan index berbeda tetap bisa dijumlahkan, hasilnya berisi NaN tanpa satu pun error, dan baris yang “menghilang” dari laporan baru disadari belakangan. Chapter ini melatih kita melacak tiga masalah index yang paling sering terjadi: NaN akibat reindex() dan alignment, index duplikat yang melipatgandakan hasil seleksi, serta kebingungan label versus posisi setelah filter.

Aturan Alignment yang Sering Terlupakan #

Saat dua Series atau dua DataFrame dioperasikan (misalnya dijumlahkan), pandas tidak membandingkan posisi baris, melainkan mencocokkan label index terlebih dahulu. Hasilnya mengikuti gabungan (union) semua label dari kedua sisi; label yang hanya ada di satu sisi diberi nilai NaN. Perilaku ini disebut alignment dan berjalan diam-diam tanpa error. Script berikut memperagakan alignment, reindex(), index duplikat, dan perbedaan label versus posisi dalam satu rangkaian debug yang bisa dijalankan ulang.

python
import pandas as pd

omzet = pd.DataFrame({"omzet": [150_000, 120_000, 95_000]}, index=["jan", "feb", "mar"])
omzet.index.name = "bulan"

print("--- reindex menambah label baru ---")
hasil = omzet.reindex(["jan", "feb", "mar", "apr"])
print(hasil)
print("label tanpa data:", hasil[hasil["omzet"].isna()].index.tolist())
print()

print("--- alignment penjumlahan dua Series ---")
januari = pd.Series({"Jakarta": 150, "Bandung": 120, "Surabaya": 95})
februari = pd.Series({"Bandung": 130, "Surabaya": 100, "Medan": 60})
total = januari + februari
print(total)
print("total mentah :", total.sum())
total = total.fillna(0)
print("setelah fillna(0):", total.to_dict())
print("total bersih :", total.sum())
print()

print("--- index duplikat ---")
transaksi = pd.DataFrame(
    {
        "kode": ["TRX-001", "TRX-002", "TRX-003"],
        "cabang": ["Jakarta", "Bandung", "Jakarta"],
        "omzet": [150_000, 120_000, 95_000],
    },
    index=[0, 1, 0],
)
print("is_unique :", transaksi.index.is_unique)
print("duplicated:", transaksi.index.duplicated().tolist())
print(transaksi.loc[0])
print("jumlah baris loc[0]:", len(transaksi.loc[0]))
print()

print("--- label vs posisi setelah filter ---")
urut = pd.DataFrame({"omzet": [10, 20, 30, 40]})
terpilih = urut[urut["omzet"] > 10]
print(terpilih)
print("label 2 :", terpilih.loc[2, "omzet"])
print("posisi 2:", terpilih.iloc[2, 0])
print()
print("setelah reset_index(drop=True):")
print(terpilih.reset_index(drop=True))
print()

print("--- loc dipakai padahal index string ---")
berlabel = pd.DataFrame({"omzet": [50, 60]}, index=["A", "B"])
try:
    berlabel.loc[0]
except KeyError as exc:
    print("loc[0]  ->", type(exc).__name__, "|", str(exc))
try:
    berlabel.iloc["A"]
except TypeError as exc:
    print('iloc["A"] ->', type(exc).__name__, "|", str(exc).splitlines()[0])

Output yang Dihasilkan:

text
--- reindex menambah label baru ---
          omzet
bulan          
jan    150000.0
feb    120000.0
mar     95000.0
apr         NaN
label tanpa data: ['apr']

--- alignment penjumlahan dua Series ---
Bandung     250.0
Jakarta       NaN
Medan         NaN
Surabaya    195.0
dtype: float64
total mentah : 445.0
setelah fillna(0): {'Bandung': 250.0, 'Jakarta': 0.0, 'Medan': 0.0, 'Surabaya': 195.0}
total bersih : 445.0

--- index duplikat ---
is_unique : False
duplicated: [False, False, True]
      kode   cabang   omzet
0  TRX-001  Jakarta  150000
0  TRX-003  Jakarta   95000
jumlah baris loc[0]: 2

--- label vs posisi setelah filter ---
   omzet
1     20
2     30
3     40
label 2 : 30
posisi 2: 40

setelah reset_index(drop=True):
   omzet
0     20
1     30
2     40

--- loc dipakai padahal index string ---
loc[0]  -> KeyError | 0
iloc["A"] -> TypeError | Cannot index by location index with a non-integer key

Mari kita bedah bagian pentingnya:

  1. omzet.reindex(["jan", "feb", "mar", "apr"]): reindex() membentuk DataFrame baru dengan label yang kita minta. Label apr tidak ada di data asli, sehingga barisnya berisi NaN, dan dtype kolom naik dari int64 ke float64 karena NaN tidak memiliki representasi integer. Ketika jumlah baris laporan tidak cocok, reindex() adalah penyebab yang layak diperiksa lebih dulu.
  2. januari + februari: kedua Series dicocokkan berdasarkan nama cabang. Hasilnya memuat union label Bandung, Jakarta, Medan, Surabaya; Jakarta bernilai NaN karena tidak ada di februari, dan Medan bernilai NaN karena tidak ada di januari. Nilai total mentah : 445.0 dihitung dengan melewatkan NaN, jadi laporan total bisa terlihat “selesai” padahal dua cabang belum dihitung.
  3. total.fillna(0): perbaikan yang disengaja setelah kita memahami kenapa NaN muncul. Langkah debugging yang benar adalah menelusuri sumber NaN dulu (cetak index, hitung isna().sum()), bukan langsung menutupnya.
  4. transaksi.loc[0]: index DataFrame ini adalah [0, 1, 0], sehingga satu pemanggilan loc[0] mengembalikan dua baris, yaitu TRX-001 dan TRX-003. Flag transaksi.index.is_unique melaporkan False dan duplicated() menandai posisi duplikat. Jika hasil seleksi lebih banyak dari perkiraan, periksa keunikan index sebelum menyalahkan logika lain.
  5. terpilih.loc[2, "omzet"] vs terpilih.iloc[2, 0]: setelah filter omzet > 10, label 2 masih menunjuk baris bernilai 30, sedangkan posisi ke-2 (iloc[2]) menunjuk baris bernilai 40. Kedua cara memilih baris berbeda hasil karena label tidak dihitung ulang setelah filter. reset_index(drop=True) mengembalikan penomoran berurutan sehingga label dan posisi kembali sejajar.
  6. berlabel.loc[0]: index DataFrame ini berupa string ["A", "B"], jadi label 0 tidak ada dan muncul KeyError. Sebaliknya berlabel.iloc["A"] melempar TypeError karena iloc hanya menerima posisi. Pesan error keduanya berbeda dan langsung mengarahkan kita ke sumber kebingungan.

Daftar Periksa Saat Hasil Operasi Tidak Sesuai #

GejalaPemeriksaan pertama
NaN banyak muncul setelah operasi antar objekBandingkan kedua index dengan index.equals() atau cetak keduanya
Jumlah baris seleksi lebih banyak dari perkiraanindex.is_unique dan index.duplicated()
Nilai berubah dtype setelah operasiNaN dari alignment menaikkan kolom integer ke float64
KeyError padahal barisnya terlihat adaApakah kodenya pakai loc padahal maksudnya iloc, atau sebaliknya

Dengan index dan alignment terkendali, sumber masalah klasik berikutnya justru terjadi ketika dua DataFrame digabungkan: jumlah baris hasil merge yang tidak sesuai ekspektasi. Chapter Debugging Merge membahas alat pemeriksaan untuk kasus itu.

Referensi resmi untuk chapter ini: Indexing and selecting data , pandas.DataFrame.reindex , dan pandas.Index.is_unique .