Perilaku dasar NaN di sekitar groupby sudah kita pelajari pada Chapter GroupBy dan Missing Values di section GroupBy dan Agregasi: kunci kelompok yang kosong dibuang secara bawaan (dropna=True), dan fungsi agregasi statistik melewati NaN sesuai skipna. Chapter ini melangkah ke sisi yang belum tersentuh: bagaimana mengisi kekosongan secara per kelompok tanpa nilai antarsensor saling bocor, dan bagaimana menuntut kelengkapan data pada saat agregasi. Semua latihan memakai data dua sensor gudang selama tujuh hari pertama Januari 2026.
Bocornya Nilai Antar Kelompok #
DataFrame berikut menyelingi pembacaan Sensor Utara dan Sensor Selatan per tanggal, sehingga urutan baris tidak lagi dikelompokkan rapi per sensor. Persoalannya muncul ketika kita memanggil ffill() langsung pada kolom:
"""Mengisi kekosongan per kelompok sensor dengan groupby ffill dan transform."""
import pandas as pd
import numpy as np
suhu_utara = [22.5, 23.1, None, 24.0, 24.8, None, 25.5]
suhu_selatan = [None, 23.9, 24.4, None, 25.0, 24.8, 25.1]
kelep_utara = [61.0, 62.0, None, 63.0, 60.0, None, 59.0]
kelep_selatan = [58.0, None, 57.0, None, 56.0, 58.0, 57.0]
gudang = pd.DataFrame(
{
"tanggal": [
f"2026-01-{d:02d}" for d in range(1, 8) for _ in range(2)
],
"sensor": ["Utara", "Selatan"] * 7,
"suhu": [v for pasangan in zip(suhu_utara, suhu_selatan) for v in pasangan],
"kelembapan": [
v for pasangan in zip(kelep_utara, kelep_selatan) for v in pasangan
],
}
)
print("Data dua sensor yang diselingi per tanggal:")
print(gudang)
print()
print("ffill() global, tanpa memandang kelompok:")
print(gudang["suhu"].ffill().tolist())
print()
print("groupby('sensor').ffill(), tiap sensor meneruskan nilainya sendiri:")
print(gudang.groupby("sensor")["suhu"].ffill())
print()
median_grup = gudang.groupby("sensor")["suhu"].transform("median")
print("Median suhu per sensor:", end=" ")
print(gudang.groupby("sensor")["suhu"].median().to_dict())
isi_median = gudang.assign(suhu=gudang["suhu"].fillna(median_grup))
print("Hasil fillna dengan median per kelompok:")
print(isi_median[["sensor", "suhu"]])
print()
ringkasan = gudang.groupby("sensor")["kelembapan"].agg(
jumlah_terisi="count",
total="sum",
total_lengkap=lambda s: s.sum(min_count=6),
)
print("Agregasi kelembapan dengan min_count=6:")
print(ringkasan)Output yang Dihasilkan:
Data dua sensor yang diselingi per tanggal:
tanggal sensor suhu kelembapan
0 2026-01-01 Utara 22.5 61.0
1 2026-01-01 Selatan NaN 58.0
2 2026-01-02 Utara 23.1 62.0
3 2026-01-02 Selatan 23.9 NaN
4 2026-01-03 Utara NaN NaN
5 2026-01-03 Selatan 24.4 57.0
6 2026-01-04 Utara 24.0 63.0
7 2026-01-04 Selatan NaN NaN
8 2026-01-05 Utara 24.8 60.0
9 2026-01-05 Selatan 25.0 56.0
10 2026-01-06 Utara NaN NaN
11 2026-01-06 Selatan 24.8 58.0
12 2026-01-07 Utara 25.5 59.0
13 2026-01-07 Selatan 25.1 57.0
ffill() global, tanpa memandang kelompok:
[22.5, 22.5, 23.1, 23.9, 23.9, 24.4, 24.0, 24.0, 24.8, 25.0, 25.0, 24.8, 25.5, 25.1]
groupby('sensor').ffill(), tiap sensor meneruskan nilainya sendiri:
0 22.5
1 NaN
2 23.1
3 23.9
4 23.1
5 24.4
6 24.0
7 24.4
8 24.8
9 25.0
10 24.8
11 24.8
12 25.5
13 25.1
Name: suhu, dtype: float64
Median suhu per sensor: {'Selatan': 24.8, 'Utara': 24.0}
Hasil fillna dengan median per kelompok:
sensor suhu
0 Utara 22.5
1 Selatan 24.8
2 Utara 23.1
3 Selatan 23.9
4 Utara 24.0
5 Selatan 24.4
6 Utara 24.0
7 Selatan 24.8
8 Utara 24.8
9 Selatan 25.0
10 Utara 24.0
11 Selatan 24.8
12 Utara 25.5
13 Selatan 25.1
Agregasi kelembapan dengan min_count=6:
jumlah_terisi total total_lengkap
sensor
Selatan 5 286.0 NaN
Utara 5 305.0 NaNMari kita telaah dua pengisian pertama pada output:
- Baris 28 (
gudang["suhu"].ffill()): pengisian global menyalin nilai baris mana pun di atasnya. Posisi kedua (Selatan, 1 Januari) terisi22.5— angka milik Sensor Utara; posisi kelima (Utara, 3 Januari) terisi23.9dari Sensor Selatan. - Baris 32 (
gudang.groupby("sensor")["suhu"].ffill()): groupby membatasi pengisian di dalam kelompoknya masing-masing. Selatan 3 Januari terisi23.1, tetapi Selatan 1 Januari tetapNaNkarena kelompok Selatan memang belum punya nilai sebelumnya.
Hasil groupby().ffill() kembali dalam urutan index asli, sehingga bisa langsung ditempelkan ke DataFrame. Pola ini penting setiap kali baris data tercampur antar kelompok, misalnya hasil penggabungan beberapa file sensor atau transaksi lintas cabang. Kelemahannya tetap ada: lubang di awal kelompok tidak terisi, dan lubang panjang tetap menyalin satu nilai lama.
Mengisi dengan Statistik Kelompok #
Alternatif yang tidak bergantung pada urutan baris adalah mengisi dengan statistik kelompok. Kombinasi groupby() dan transform("median") menghasilkan Series sepanjang data asli yang berisi median kelompok masing-masing baris:
- Baris 35 (
transform("median")): median Sensor Utara24.0(dari 22.5, 23.1, 24.0, 24.8, 25.5) dan Sensor Selatan24.8(dari 23.9, 24.4, 25.0, 24.8, 25.1). - Baris 38 (
gudang["suhu"].fillna(median_grup)): setiapNaNdiganti median kelompoknya sendiri; Selatan 1 Januari terisi24.8, bukan24.0milik Utara.
Median kelompok lebih tahan terhadap outlier daripada mean, dan transform menjamin hasilnya sejajar dengan baris asli tanpa perlu merge. Bila suatu kelompok kehilangan hampir semua nilainya, statistik kelompok pun menjadi tidak bisa dipercaya — saat itu baris-barisnya lebih baik dibiarkan kosong atau dibuang.
Menuntut Kelengkapan pada Agregasi #
Pengisian bukan satu-satunya keputusan; kadang kita justru ingin agregasi menolak angka yang dibangun dari data setengah jadi. Parameter min_count pada sum() menetapkan jumlah nilai terisi minimum agar hasilnya sah. Baris 43-47 pada skrip mengagregasi kelembapan dengan tiga ukuran sekaligus: count menghitung lima nilai terisi per sensor, sum tetap menghasilkan total 305.0 untuk Utara dan 286.0 untuk Selatan, sedangkan sum(min_count=6) menghasilkan NaN untuk keduanya karena tidak ada kelompok yang mencapai enam nilai terisi. Dengan begitu laporan otomatis menandai angka yang tidak layak dipakai, alih-alih menampilkannya seolah lengkap.
Rujukan lengkap perilaku groupby ada pada panduan GroupBy
dan pandas.DataFrame.groupby
; perilaku NaN pada kunci kelompok tetap merujuk panduan Working with missing data
.
Sampai di sini lubang missing sudah kita tangani dalam skala kolom maupun kelompok. Tantangan berikutnya berbeda sifat: nilai yang justru terisi tetapi salah, seperti pembacaan sensor yang melompat ke 41.0 derajat. Dua chapter berikutnya membekali kita dengan dua metode deteksi nilai ekstrem, dimulai dari metode IQR yang paling tahan banting.