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():
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:
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: float64Mari kita telaah bagian per bagian:
- Baris 3-6: DataFrame enam baris dengan dua jenis lubang;
kotabertipestrdengan duaNaN, dannilaibertipefloat64dengan duaNaN. - Baris 10-11:
sum()dengan defaultdropna=Truehanya menghasilkan Bandung 60.0 dan Jakarta 100.0; baris kunciNaNtidak muncul sama sekali. - Baris 13-14: dengan
dropna=False, muncul kelompok berlabelNaNdengan total 120.0, yaitu 80.0 + 40.0 dari dua baris tanpa kota. - Baris 16-20:
count()menghitung baris dengan nilai terisi sehingga hanya 1 per kelompok, sedangkansize()menghitung semua baris sehingga 2 per kelompok. - 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.