Data nyata hampir selalu memiliki lubang: kolom kunci yang kosong atau nilai yang belum tercatat. GroupBy punya aturan bawaan yang konsisten untuk kedua situasi tersebut, tetapi aturan ini sering mengejutkan pemula, misalnya baris dengan kunci kosong yang tiba-tiba tidak muncul di hasil agregasi. Chapter ini membedah dua posisi NaN yang berbeda: pada kolom kunci kelompok dan pada kolom nilai yang diagregasi. Kita juga membandingkan count() dengan size() dan menutup dengan strategi fillna() sebelum mengelompokkan.

Dua Posisi NaN dalam Alur GroupBy #

Dalam satu operasi groupby, NaN bisa muncul di dua tempat yang maknanya berbeda. Pertama, NaN pada kolom kunci: baris tersebut tidak punya identitas kelompok, sehingga groupby tidak tahu harus menaruhnya di mana. Kedua, NaN pada kolom nilai: baris tetap punya kelompok yang jelas, tetapi angka yang mau dijumlahkan tidak tersedia. Pandas menangani kedua kasus dengan aturan yang berbeda, dan keduanya akan kita amati pada satu dataset kecil di bawah ini.

Kunci NaN Dibuang secara Default #

Secara bawaan, groupby memakai dropna=True: baris yang kolom kuncinya NaN dibuang sebelum pengelompokan dimulai. Perilaku ini mencegah munculnya “kelompok hantu” yang isinya campuran data tanpa identitas. Konsekuensinya, hasil agregasi bisa memiliki total yang lebih kecil dari total data asli tanpa ada peringatan apa pun. Kita wajib menyadari hal ini saat membaca laporan, karena jumlah yang hilang tidak terlihat di output sama sekali.

Menyertakan Kelompok NaN dengan dropna=False #

Jika baris tanpa kunci tetap penting untuk dianalisis, kita bisa memakai dropna=False saat memanggil groupby(). Pandas lalu membentuk satu kelompok tersendiri berlabel NaN yang berisi semua baris tanpa identitas. Skrip berikut menampilkan kedua mode tersebut sekaligus dengan perilaku count(), size(), dan fillna():

python
import pandas as pd

data = pd.DataFrame({
    "kota": ["Jakarta", "Jakarta", None, "Bandung", "Bandung", None],
    "nilai": [100.0, None, 80.0, 60.0, None, 40.0],
})

print(data, "\n")

print("sum() dengan kunci kelompok NaN (default dropna=True):")
print(data.groupby("kota")["nilai"].sum(), "\n")

print("sum() dengan dropna=False:")
print(data.groupby("kota", dropna=False)["nilai"].sum(), "\n")

print("count() menghitung baris dengan nilai tidak NaN:")
print(data.groupby("kota")["nilai"].count(), "\n")

print("size() menghitung seluruh baris kelompok:")
print(data.groupby("kota").size(), "\n")

data["kota"] = data["kota"].fillna("Tidak Diketahui")
print("hasil setelah fillna pada kolom kota:")
print(data.groupby("kota")["nilai"].sum())

Output yang Dihasilkan:

text
      kota  nilai
0  Jakarta  100.0
1  Jakarta    NaN
2      NaN   80.0
3  Bandung   60.0
4  Bandung    NaN
5      NaN   40.0 

sum() dengan kunci kelompok NaN (default dropna=True):
kota
Bandung     60.0
Jakarta    100.0
Name: nilai, dtype: float64 

sum() dengan dropna=False:
kota
Bandung     60.0
Jakarta    100.0
NaN        120.0
Name: nilai, dtype: float64 

count() menghitung baris dengan nilai tidak NaN:
kota
Bandung    1
Jakarta    1
Name: nilai, dtype: int64 

size() menghitung seluruh baris kelompok:
kota
Bandung    2
Jakarta    2
dtype: int64 

hasil setelah fillna pada kolom kota:
kota
Bandung             60.0
Jakarta            100.0
Tidak Diketahui    120.0
Name: nilai, dtype: float64

Mari kita telaah bagian per bagian:

  1. Baris 3-6: DataFrame enam baris dengan dua jenis lubang; kota bertipe str dengan dua NaN, dan nilai bertipe float64 dengan dua NaN.
  2. Baris 10-11: sum() dengan default dropna=True hanya menghasilkan Bandung 60.0 dan Jakarta 100.0; baris kunci NaN tidak muncul sama sekali.
  3. Baris 13-14: dengan dropna=False, muncul kelompok berlabel NaN dengan total 120.0, yaitu 80.0 + 40.0 dari dua baris tanpa kota.
  4. Baris 16-20: count() menghitung baris dengan nilai terisi sehingga hanya 1 per kelompok, sedangkan size() menghitung semua baris sehingga 2 per kelompok.
  5. Baris 22-24: fillna("Tidak Diketahui") mengisi kunci kosong dengan label eksplisit, sehingga 120.0 kini muncul sebagai kelompok bernama.

NaN pada Nilai yang Diagregasi #

Untuk NaN pada kolom nilai, aturan bawaannya berbeda: fungsi agregasi statistik seperti sum(), mean(), min(), dan max() melewati NaN secara bawaan (perilaku skipna). Pada output di atas, sum() Jakarta menghasilkan 100.0, bukan NaN, karena satu-satunya nilai terisi yang dijumlahkan adalah 100.0. Perilaku ini umumnya diinginkan, tetapi menimbulkan jebakan halus saat kita menggabungkannya dengan penghitungan baris.

Strategi Menangani NaN sebelum GroupBy #

Ada dua strategi dasar, dan pilihannya bergantung pada makna data. Jika kunci kosong memang tidak informatif, biarkan default dropna=True bekerja dan catat bahwa hasilnya hanya mencakup baris berlabel. Jika kunci kosong sebenarnya punya arti bisnis, seperti transaksi tanpa data cabang, isi dulu dengan fillna() memakai label yang jelas seperti "Tidak Diketahui" agar kelompok tersebut muncul dan bisa diperhitungkan. Pola fillna() yang lebih lengkap sudah kita bahas pada Section Data Cleaning, dan dokumentasinya tersedia pada panduan missing data serta panduan GroupBy ; daftar lengkap opsi groupby() ada di DataFrame.groupby .

Dengan memahami kedua posisi NaN, kita kini bisa mempercayai angka hasil agregasi dan menjelaskan dari mana selisihnya bila data tidak lengkap. Selanjutnya kita memanfaatkan transform() yang sudah dikenal pada Chapter transform() pada GroupBy untuk keperluan yang sangat umum di dunia kerja: menghitung persentase kontribusi setiap baris terhadap kelompoknya.