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:

PolaKepanjanganNilai yang hilang bergantung padaContoh di gudang
MCARMissing Completely At RandomTidak bergantung pada apa punSensor sesekali gagal mengirim satu pembacaan acak
MARMissing At RandomKolom lain yang terobservasiPencatatan kelembapan berhenti saat sensor standby
MNARMissing Not At RandomNilainya sendiri yang tak terobservasiSensor 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:

python
"""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:

text
       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      2

Mari kita telaah baris demi baris:

  1. Baris 4-24 (DataFrame gudang): empat belas baris log dengan tiga kolom yang lubangnya dirancang berbeda; suhu kehilangan empat sel tersebar, kelembapan kehilangan tiga sel, dan catatan hampir selalu kosong.
  2. Baris 30 (gudang.isna().sum()): menghitung sel kosong per kolom; suhu kehilangan 4 dari 14 sel atau sekitar 29 persen menurut baris 34 yang memakai isna().mean() * 100.
  3. Baris 37-38 (pola baris): setiap baris diubah menjadi rangkaian tiga digit, misalnya 101 berarti suhu dan catatan kosong sementara kelembapan terisi; value_counts() lalu menunjukkan pola 001 mendominasi dengan tujuh baris.
  4. Baris 43-45 (uji MAR): groupby("status") menghitung persentase kelembapan kosong per status; hasilnya 75 persen pada kelompok standby dan 0 persen pada aktif.
  5. Baris 50-52 (uji indikasi MNAR): pd.crosstab menyilangkan 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().