Section Data Cleaning sudah mengajarkan cara mendeteksi dan mengisi sel kosong, tetapi belum menjawab satu pertanyaan yang lebih mendasar: mengapa data itu hilang? Penanya menentukan nasib analisis, karena dua lubang yang tampak sama di layar bisa punya makna statistik yang berbeda. Chapter pembuka section ini memperkenalkan tiga pola kehilangan data yang menjadi acuan statistik, yaitu MCAR, MAR, dan MNAR, lalu mempraktikkan cara mengintip pola tersebut dari dalam pandas pada log sensor gudang. Di akhir chapter, kita bisa menghubungkan pola yang terlihat dengan strategi penanganan yang paling jujur.
Tiga Pola Kehilangan Data #
Literatur statistik yang dipopulerkan Donald Rubin membagi mekanisme kehilangan data menjadi tiga kategori. Kategorinya penting dipahami karena setiap kategori menuntut perlakuan berbeda:
| Pola | Kepanjangan | Nilai yang hilang bergantung pada | Contoh di gudang |
|---|---|---|---|
| MCAR | Missing Completely At Random | Tidak bergantung pada apa pun | Sensor sesekali gagal mengirim satu pembacaan acak |
| MAR | Missing At Random | Kolom lain yang terobservasi | Pencatatan kelembapan berhenti saat sensor standby |
| MNAR | Missing Not At Random | Nilainya sendiri yang tak terobservasi | Sensor berhenti mencatat justru saat suhu paling ekstrem |
Namanya memang menyesatkan bagi pemula: MAR tetap berarti data hilang secara sistematis, hanya saja penyebabnya dapat dijelaskan dari kolom lain yang terlihat. MNAR adalah pola paling berbahaya karena penyebabnya tersembunyi di nilai yang justru tidak tercatat, sehingga data yang tersisa tidak lagi mewakili populasi. Catatan lengkap mekanisme kekosongan di pandas tersedia pada panduan Working with missing data .
Mengintip Pola pada Log Gudang #
Skrip berikut membangun log pencatatan suhu dan kelembapan selama dua minggu di Gudang Kopi Aroma Nusantara, lengkap dengan kolom status sensor dan catatan petugas. Skrip ini runnable dan kita bedah setelah outputnya:
"""Mengenali pola kekosongan (MCAR, MAR, MNAR) pada log sensor gudang."""
import pandas as pd
gudang = pd.DataFrame(
{
"tanggal": [f"2026-01-{d:02d}" for d in range(1, 15)],
"status": [
"aktif", "aktif", "standby", "aktif", "aktif", "standby", "aktif",
"aktif", "standby", "aktif", "aktif", "aktif", "aktif", "standby",
],
"suhu": [
22.5, 23.1, None, 24.0, 24.8, None, None,
25.5, 41.0, 25.2, 12.0, 23.8, 24.1, None,
],
"kelembapan": [
61.0, 62.0, 62.5, 63.0, 60.0, None, 58.5,
59.0, None, 58.0, 90.0, 61.0, 60.0, None,
],
"catatan": [
"Suhu normal", None, None, None, "Kelembapan turun", None, None,
None, None, None, None, None, None, None,
],
}
)
print(gudang)
print()
print("Jumlah sel kosong per kolom:")
print(gudang.isna().sum())
print()
print("Persentase sel kosong per kolom:")
print((gudang.isna().mean() * 100).round(1))
print()
pola = gudang[["suhu", "kelembapan", "catatan"]].isna().astype(int).astype(str)
pola = pola.agg("".join, axis=1)
print("Distribusi pola kekosongan (suhu-kelembapan-catatan, 1 = kosong):")
print(pola.value_counts())
print()
rasio = gudang.groupby("status")["kelembapan"].agg(
kosong_persen=lambda s: round(s.isna().mean() * 100, 1)
)
print("Persentase kelembapan kosong per status sensor:")
print(rasio)
print()
ekstrem = (gudang["suhu"] > 30) | (gudang["suhu"] < 15)
print("Silang baris tanpa catatan vs suhu ekstrem:")
print(pd.crosstab(gudang["catatan"].isna(), ekstrem))Output yang Dihasilkan:
tanggal status suhu kelembapan catatan
0 2026-01-01 aktif 22.5 61.0 Suhu normal
1 2026-01-02 aktif 23.1 62.0 NaN
2 2026-01-03 standby NaN 62.5 NaN
3 2026-01-04 aktif 24.0 63.0 NaN
4 2026-01-05 aktif 24.8 60.0 Kelembapan turun
5 2026-01-06 standby NaN NaN NaN
6 2026-01-07 aktif NaN 58.5 NaN
7 2026-01-08 aktif 25.5 59.0 NaN
8 2026-01-09 standby 41.0 NaN NaN
9 2026-01-10 aktif 25.2 58.0 NaN
10 2026-01-11 aktif 12.0 90.0 NaN
11 2026-01-12 aktif 23.8 61.0 NaN
12 2026-01-13 aktif 24.1 60.0 NaN
13 2026-01-14 standby NaN NaN NaN
Jumlah sel kosong per kolom:
tanggal 0
status 0
suhu 4
kelembapan 3
catatan 12
dtype: int64
Persentase sel kosong per kolom:
tanggal 0.0
status 0.0
suhu 28.6
kelembapan 21.4
catatan 85.7
dtype: float64
Distribusi pola kekosongan (suhu-kelembapan-catatan, 1 = kosong):
001 7
000 2
101 2
111 2
011 1
Name: count, dtype: int64
Persentase kelembapan kosong per status sensor:
kosong_persen
status
aktif 0.0
standby 75.0
Silang baris tanpa catatan vs suhu ekstrem:
suhu False True
catatan
False 2 0
True 10 2Mari kita telaah baris demi baris:
- Baris 4-24 (DataFrame
gudang): empat belas baris log dengan tiga kolom yang lubangnya dirancang berbeda;suhukehilangan empat sel tersebar,kelembapankehilangan tiga sel, dancatatanhampir selalu kosong. - Baris 30 (
gudang.isna().sum()): menghitung sel kosong per kolom;suhukehilangan 4 dari 14 sel atau sekitar 29 persen menurut baris 34 yang memakaiisna().mean() * 100. - Baris 37-38 (pola baris): setiap baris diubah menjadi rangkaian tiga digit, misalnya
101berartisuhudancatatankosong sementarakelembapanterisi;value_counts()lalu menunjukkan pola001mendominasi dengan tujuh baris. - Baris 43-45 (uji MAR):
groupby("status")menghitung persentasekelembapankosong per status; hasilnya 75 persen pada kelompokstandbydan 0 persen padaaktif. - Baris 50-52 (uji indikasi MNAR):
pd.crosstabmenyilangkan baris tanpa catatan dengan suhu ekstrem; dua dari dua suhu ekstrem berada di kelompok tanpa catatan.
Membaca Hasil Diagnosa #
Angka pada kelompok standby memberi indikasi MAR yang kuat: kelembapan tidak pernah hilang pada sensor aktif, tetapi hilang pada tiga dari empat hari standby. Kekosongan tersebut dapat dijelaskan oleh kolom lain yang terobservasi, sehingga kita bisa mengisinya per kelompok atau mengecualikan kelompok standby dari analisis kelembapan. Tabel silang memberi sinyal sebaliknya: baris bersuhu ekstrem (41.0 dan 12.0) selalu tanpa catatan, sementara baris bertanda catatan semuanya bernilai normal. Pola semacam ini mengarah ke MNAR karena kekosongan berkumpul di nilai-nilai istimewa.
Mengapa Pola Menentukan Strategi #
Pola yang terbaca langsung menentukan langkah selanjutnya. Pada MCAR, menghapus baris lewat dropna() relatif aman karena baris tersisa masih mewakili populasi, hanya jumlahnya berkurang. Pada MAR, pengisian per kelompok memberi hasil lebih jujur daripada pengisian global, misalnya median suhu per sensor alih-alih median seluruh gudang; teknik ini kita praktikkan pada Chapter Missing Data pada GroupBy. Pada MNAR, sebaiknya kita tidak bermain pembuat kode semata: dokumentasikan asumsi, pertahankan baris asli, dan cari sumber data cadangan bila memungkinkan.
Dengan kerangka tiga pola ini, chapter berikutnya langsung praktik pada strategi pengisian yang paling sering dipakai untuk data berurutan seperti log sensor: meneruskan nilai dari baris sebelumnya lewat ffill(), atau menariknya dari baris sesudahnya lewat bfill().