Nilai yang hilang

Mendeteksi nilai yang hilang, memutuskan strategi penanganannya, dan mendokumentasikan keputusan tersebut.

Daftar isi

Nilai yang hilang adalah sel data yang tidak terisi, ditandai NaN pada kolom numerik atau None pada kolom teks. Topik ini membahas cara mendeteksinya dengan isna, akibat yang ditimbulkannya pada perhitungan, pilihan antara dropna dan fillna, serta cara mendokumentasikan keputusan penanganannya. Setelah topik ini, data dengan sel kosong dapat ditangani secara sengaja dan tercatat, bukan secara diam-diam.

Mendeteksi nilai yang hilang#

DataFrame contoh berisi catatan peminjaman perpustakaan dengan dua kolom yang tidak terisi sempurna. Not a Number (NaN) adalah penanda sel kosong pada kolom numerik, sedangkan nilai None dari Python tetap tampil sebagai None pada kolom teks. Keduanya diperlakukan sebagai nilai yang hilang oleh pandas.

python Menampilkan data peminjaman dengan sel kosong
import pandas as pd

pinjam = pd.DataFrame({
    "anggota": ["Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"],
    "judul": ["Atlas Dunia", "Kamus Biologi", None, "Ensiklopedia Sains", "Atlas Dunia", None],
    "lama_hari": [7, 10, 5, None, None, 12],
})

print(pinjam)
text Keluaran
  anggota               judul  lama_hari
0     Ali         Atlas Dunia        7.0
1    Budi       Kamus Biologi       10.0
2   Citra                None        5.0
3    Dewi  Ensiklopedia Sains        NaN
4     Eka         Atlas Dunia        NaN
5   Fajar                None       12.0

Perhatikan perbedaan penandanya. Sel kosong pada kolom lama_hari tampil sebagai NaN dan seluruh kolom berubah menjadi float64, sedangkan sel kosong pada kolom judul tetap tampil sebagai None. Penjumlahan kolom berisi None tidak mungkin, sehingga pandas mengonversinya menjadi NaN pada kolom numerik agar perhitungan tetap bisa dijalankan.

Perintah isna mengembalikan tabel boolean dengan ukuran yang sama, berisi True untuk sel kosong dan False untuk sel terisi. Penjumlahan atas tabel boolean itu, yaitu isna().sum(), menghitung jumlah sel kosong per kolom dan menjadi jawaban atas pertanyaan pertama yang diajukan saat data diperiksa.

python Menghitung sel kosong per kolom
import pandas as pd

pinjam = pd.DataFrame({
    "anggota": ["Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"],
    "judul": ["Atlas Dunia", "Kamus Biologi", None, "Ensiklopedia Sains", "Atlas Dunia", None],
    "lama_hari": [7, 10, 5, None, None, 12],
})

print(pinjam.isna().sum())
text Keluaran
anggota      0
judul        2
lama_hari    2
dtype: int64

Hasilnya menyebutkan dua sel kosong pada kolom judul dan dua pada kolom lama_hari, sedangkan kolom anggota terisi penuh. Jumlah inilah dasar semua keputusan berikutnya. Data dengan satu atau dua sel kosong pada ribuan baris menuntut strategi yang berbeda daripada data dengan kolom yang kosong sebagian besar isinya.

Akibat pada perhitungan#

Nilai yang hilang bukan nol, dan perbedaannya terlihat pada perhitungan. Fungsi agregasi seperti mean dan sum melewati sel kosong secara default, sehingga rata-rata lama_hari dihitung hanya dari empat nilai yang terisi. Argumen skipna=False mematikan perilaku itu, dan hasilnya berubah menjadi NaN karena sel kosong ikut dihitung.

python Pengaruh sel kosong pada agregasi dan perbandingan
import pandas as pd

pinjam = pd.DataFrame({
    "anggota": ["Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"],
    "judul": ["Atlas Dunia", "Kamus Biologi", None, "Ensiklopedia Sains", "Atlas Dunia", None],
    "lama_hari": [7, 10, 5, None, None, 12],
})

print(pinjam["lama_hari"].mean())
print(pinjam["lama_hari"].sum(skipna=False))
print()
print(pinjam["lama_hari"] > 6)
text Keluaran
8.5
nan

0     True
1     True
2    False
3    False
4    False
5     True
Name: lama_hari, dtype: bool

Bagian ketiga keluaran menunjukkan akibat yang lebih halus. Perbandingan lama_hari > 6 menghasilkan False untuk sel kosong, sehingga penyaringan dengan syarat semacam itu diam-diam membuang baris yang nilainya belum diketahui. Baris Dewi dan Eka tidak memenuhi syarat bukan karena nilainya kecil, melainkan karena nilainya tidak ada. Perilaku ini wajar untuk sebagian analisis dan menyesatkan untuk yang lain, sehingga perlu disadari sebelum penyaringan dijalankan.

Memilih antara dropna dan fillna#

Dua strategi dasar menangani nilai yang hilang adalah membuang barisnya dengan dropna atau mengisinya dengan fillna. Tanpa argumen tambahan, dropna membuang setiap baris yang memuat minimal satu sel kosong. Pada data contoh yang hanya berisi enam baris, perilaku itu menyisakan dua baris, sehingga empat catatan hilang begitu saja.

python Membuang baris dengan dropna dan versi terbatasnya
import pandas as pd

pinjam = pd.DataFrame({
    "anggota": ["Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"],
    "judul": ["Atlas Dunia", "Kamus Biologi", None, "Ensiklopedia Sains", "Atlas Dunia", None],
    "lama_hari": [7, 10, 5, None, None, 12],
})

print(pinjam.dropna())
print()
print(pinjam.dropna(subset=["judul"]))
text Keluaran
  anggota          judul  lama_hari
0     Ali    Atlas Dunia        7.0
1    Budi  Kamus Biologi       10.0

  anggota               judul  lama_hari
0     Ali         Atlas Dunia        7.0
1    Budi       Kamus Biologi       10.0
3    Dewi  Ensiklopedia Sains        NaN
4     Eka         Atlas Dunia        NaN

Argumen subset membatasi kolom yang diperiksa. Dengan dropna(subset=["judul"]), hanya baris dengan judul kosong yang dibuang, sehingga catatan Dewi dan Eka tetap ada meskipun lama_hari tidak terisi. Pilihan ini lebih presisi dan biasanya lebih aman daripada dropna tanpa argumen. Kehilangan dua dari enam baris sudah terasa besar, dan pembuangan baris dalam jumlah besar pada data nyata dapat mengubah hasil analisis tanpa terlihat.

Alternatif kedua adalah mengisi sel kosong dengan nilai tertentu. Nilai isian harus masuk akal untuk kolomnya, misalnya median untuk kolom angka dan teks penanda untuk kolom judul. Contoh berikut mengisi kedua kolom dengan strategi yang berbeda pada salinan data, agar data asli tetap utuh sebagai pembanding.

python Mengisi sel kosong per kolom dengan fillna
import pandas as pd

pinjam = pd.DataFrame({
    "anggota": ["Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"],
    "judul": ["Atlas Dunia", "Kamus Biologi", None, "Ensiklopedia Sains", "Atlas Dunia", None],
    "lama_hari": [7, 10, 5, None, None, 12],
})

bersih = pinjam.copy()
bersih["judul"] = bersih["judul"].fillna("Judul tidak tercatat")
bersih["lama_hari"] = bersih["lama_hari"].fillna(bersih["lama_hari"].median())
print(bersih)
text Keluaran
  anggota                 judul  lama_hari
0     Ali           Atlas Dunia        7.0
1    Budi         Kamus Biologi       10.0
2   Citra  Judul tidak tercatat        5.0
3    Dewi    Ensiklopedia Sains        8.5
4     Eka           Atlas Dunia        8.5
5   Fajar  Judul tidak tercatat       12.0

Kolom lama_hari diisi dengan median dari nilai yang terisi, sedangkan kolom judul diisi dengan teks penanda. Hasil akhirnya berukuran enam baris utuh, dan setiap selnya kini dapat dihitung atau disaring tanpa perilaku tersembunyi. Isian teks penanda tetap perlu ditandai sebagai data tanpa informasi, sebab baris dengan “Judul tidak tercatat” bukan judul sebenarnya, melainkan ketiadaan informasi judul yang diberi nama.

Keputusan per kolom dan dokumentasinya#

Penanganan nilai yang hilang berakhir pada keputusan yang berbeda untuk setiap kolom, dan keputusan itu mengikuti makna kolomnya. Judul buku yang tidak tercatat tidak dapat ditebak, sehingga diisi teks penanda. Lama pinjam yang kosong dapat diisi median karena nilainya berkisar sempit, atau dibuang bila analisis menuntut data lengkap. Kolom anggota yang terisi penuh tidak membutuhkan perlakuan apa pun.

KolomSel kosongKeputusanAlasan
anggota0tanpa perlakuankolom terisi penuh
judul2isi “Judul tidak tercatat”judul tidak dapat ditebak
lama_hari2isi mediannilai berkisar sempit dan digunakan untuk perhitungan

Tabel semacam ini menjadi dokumentasi strategi. Tanpa dokumentasi, pembaca laporan tidak dapat membedakan antara nilai median hasil pengisian dan data asli, dan analisis yang diulang di kemudian hari dapat mengambil keputusan yang berbeda. Dokumentasi yang cukup mencatat kolom, jumlah sel kosong, keputusan, dan alasannya. Catatan yang sama dapat ditulis sebagai komentar pada kode pembersihan, sehingga keputusan dan eksekusinya berada di satu tempat.

Kesalahan umum#

Kesalahan pertama adalah mengisi sel kosong dengan nol tanpa alasan. Metode fillna(0) mudah ditulis dan hasilnya terlihat rapi, tetapi nol memiliki makna sendiri pada kebanyakan kolom, misalnya peminjaman dengan lama nol hari. Statistik yang dihitung dari data berisi nol hasil pengisian menjadi salah secara diam-diam, dan kesalahan ini jarang muncul pada pesan error apa pun.

Kesalahan kedua adalah menjalankan dropna tanpa subset pada data yang lebar. Satu kolom yang tidak relevan untuk analisis, misalnya kolom catatan tambahan yang jarang terisi, cukup untuk membuang baris yang sebenarnya lengkap. Batasi kolom yang diperiksa pada kolom yang digunakan analisisnya.

Kesalahan ketiga adalah membandingkan kolom dengan None untuk mencari sel kosong. Ekspresi pinjam["lama_hari"] == None menghasilkan False untuk semua baris termasuk yang kosongnya NaN, karena perbandingan dengan NaN selalu False. Fungsi isna adalah cara yang tepat untuk mendeteksi sel kosong, baik berupa NaN maupun None.

Ringkasan#

  • isna().sum() menghitung sel kosong per kolom dan menjadi dasar semua keputusan penanganannya.
  • dropna membuang baris dengan subset untuk membatasi kolom yang diperiksa, sedangkan fillna mengisi sel kosong dengan nilai yang masuk akal untuk kolomnya.
  • Nilai kosong berperilaku khusus pada perhitungan, yaitu dilewati agregasi dan selalu False pada perbandingan, sehingga penyaringan perlu diperiksa ulang.
  • Setiap keputusan didokumentasikan per kolom beserta alasannya, misalnya dalam bentuk tabel keputusan.

Referensi#