DataLens

Menangani data yang hilang (NaN, fillna, dropna)

Temukan nilai yang hilang, pahami pengaruhnya pada perhitungan, dan putuskan secara sadar apakah akan mengisi, membuang atau menandainya.

Level: Menengah · 11 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Idenya

Data sungguhan punya celah: seorang customer melewatkan isian kota, sebuah sensor mati, harga belum dimasukkan. pandas menandai nilai yang hilang sebagai NaN ("not a number"), atau None/NaT di beberapa tipe. Data yang hilang bukan gangguan yang harus dihapus; ia adalah informasi. Mengapa datanya hilang, dan apa yang harus dilakukan analisismu terhadapnya? Setiap pilihan (mengisi, membuang, menyimpan dan menandai) mengubah angkamu, jadi pilihan itu harus disengaja.

import pandas as pd
import numpy as np

customers = pd.DataFrame({
    "name": ["Ayu", "Budi", "Citra", "Dimas", "Eka", "Fajar"],
    "city": ["Jakarta", "Bandung", None, "Jakarta", np.nan, "Medan"],
    "orders": [3, np.nan, 5, 1, 2, np.nan],
    "spent": [1245000, 1145000, np.nan, 550000, 410000, 0],
})
print(customers)
    name     city  orders      spent
0    Ayu  Jakarta     3.0  1245000.0
1   Budi  Bandung     NaN  1145000.0
2  Citra     None     5.0        NaN
3  Dimas  Jakarta     1.0   550000.0
4    Eka      NaN     2.0   410000.0
5  Fajar    Medan     NaN        0.0

None dan np.nan sama-sama tampil sebagai nilai hilang. Perhatikan orders berubah menjadi float (3.0): kolom integer NumPy tidak bisa menyimpan NaN, jadi pandas menaikkannya menjadi float.

Menemukan nilai yang hilang

Modul ini memakai tabel customer kecil yang punya beberapa celah. Sebelum kamu bisa memutuskan apa yang dilakukan terhadap nilai yang hilang, kamu perlu tahu di mana letaknya dan berapa banyak. .isna() menandai setiap sel yang kosong, dan menjumlahkan tanda-tanda itu memberi hitungan per kolom:

print(customers.isna().sum())
print(customers[customers["city"].isna()])
name      0
city      2
orders    2
spent     1
dtype: int64
    name  city  orders     spent
2  Citra  None     5.0       NaN
4    Eka   NaN     2.0  410000.0

.isna() (atau kembarannya .isnull()) memberi True di tempat nilainya hilang; .notna() kebalikannya. Jumlahkan untuk menghitung per kolom, atau pakai sebagai mask untuk melihat baris yang terdampak. Melihat barisnya itu penting: mungkin semua kota yang hilang berasal dari satu formulir pendaftaran, yang memberi tahu sesuatu tentang penyebabnya.

Awas

NaN tidak sama dengan apa pun, termasuk dirinya sendiri. customers["city"] == np.nan bernilai False untuk setiap baris. Selalu pakai .isna() untuk menguji nilai yang hilang.

Bagaimana nilai yang hilang memengaruhi perhitungan

Inilah alasan nilai yang hilang perlu diputuskan, bukan diabaikan. Method ringkasan pandas diam-diam melewati nilai yang hilang, jadi jawaban yang kamu dapat bergantung pada apa arti celah-celah itu menurutmu:

print(customers["orders"].sum())
print(customers["orders"].mean())
print(customers["orders"].count(), len(customers))
11.0
2.75
4 6

Rata-ratanya 11 / 4 = 2,75, dihitung dari 4 nilai yang diketahui, bukan dari keenam customer. Itu sering masuk akal, tapi tidak selalu: kalau orders yang hilang sebenarnya berarti "nol order", rata-rata yang sebenarnya adalah 11 / 6 ≈ 1,83. Data yang sama, dua jawaban berbeda, tergantung apa arti nilai yang hilang. Itulah sebabnya kamu harus memutuskan.

Perhatikan juga bahwa spent bernilai 0 untuk Fajar dan NaN untuk Citra. Itu fakta yang berbeda: Fajar tidak membelanjakan apa pun; untuk Citra, kita tidak tahu.

Opsi 1: buang

Tanggapan paling sederhana adalah membuang baris (atau kolom) yang punya nilai hilang. Ini juga yang paling mudah berlebihan, jadi perhatikan berapa baris yang tersisa di setiap versi:

print(len(customers.dropna()))
print(len(customers.dropna(subset=["city"])))
print(customers.dropna(axis="columns").columns.tolist())
2
4
['name']

Buang kalau baris yang hilang tidak bisa dipakai untuk pertanyaan yang sedang dijawab (kamu tidak bisa memetakan customer tanpa kota) dan kalau jumlahnya sedikit. Membuang 40% dataset bisa diam-diam membuat hasilmu bias.

Opsi 2: isi

Pilihan lainnya adalah menyimpan barisnya dan mengisi setiap celah dengan nilai yang masuk akal. Apa yang masuk akal tergantung kolomnya: kota yang hilang bisa menjadi "Unknown", dan jumlah order yang hilang mungkin sebenarnya berarti nol:

filled = customers.fillna({"city": "Unknown", "orders": 0})
print(filled[["name", "city", "orders"]])
    name     city  orders
0    Ayu  Jakarta     3.0
1   Budi  Bandung     0.0
2  Citra  Unknown     5.0
3  Dimas  Jakarta     1.0
4    Eka  Unknown     2.0
5  Fajar    Medan     0.0

fillna dengan dictionary mengisi setiap kolom dengan nilainya masing-masing. Pilihan yang umum:

Isi denganKapan
label seperti "Unknown"kategori, supaya barisnya tetap muncul di total per kelompok
0saat hilang benar-benar berarti tidak ada (tidak ada order tercatat = nol order)
median atau meannilai angka untuk perkiraan cepat; median tahan terhadap outlier
nilai sebelumnya (ffill)time series, di mana nilai terakhir yang diketahui masih berlaku
median_spent = customers["spent"].median()
customers["spent_filled"] = customers["spent"].fillna(median_spent)
print(median_spent, customers["spent_filled"].tolist())
550000.0 [1245000.0, 1145000.0, 550000.0, 550000.0, 410000.0, 0.0]

Mengisi dengan median mempertahankan barisnya, tapi itu mengarang data. Kolomnya juga jadi terlihat lebih pasti dari kenyataannya. Pakai dengan sadar, dan biasanya bersama sebuah penanda.

Forward fill untuk time series

Untuk nilai yang dicatat dari waktu ke waktu, seperti jumlah stok harian, sering ada tebakan yang lebih baik daripada angka tetap. Kalau seseorang lupa menghitung stok hari Selasa, tebakan terbaiknya biasanya angka hari Senin, karena stok tidak berubah kecuali terjadi sesuatu:

stock = pd.Series([24, np.nan, np.nan, 18, np.nan], index=["Mon", "Tue", "Wed", "Thu", "Fri"])
print(stock.ffill().tolist())
[24.0, 24.0, 24.0, 18.0, 18.0]

.ffill() membawa nilai terakhir yang diketahui ke depan: kalau tidak ada yang mencatat stok hari Selasa, kemungkinan besar stoknya masih 24. .bfill() mengisi ke belakang. .interpolate() memperkirakan nilai di antaranya.

Opsi 3: simpan dan tandai

Sering kali langkah terbaik adalah menyimpan nilai yang hilang dan mencatat bahwa nilainya hilang, supaya langkah berikutnya (dan pembaca) bisa melihatnya:

customers["city_missing"] = customers["city"].isna()
print(customers[["name", "city", "city_missing"]])
    name     city  city_missing
0    Ayu  Jakarta         False
1   Budi  Bandung         False
2  Citra     None          True
3  Dimas  Jakarta         False
4    Eka      NaN          True
5  Fajar    Medan         False

Kolom penanda memungkinkanmu melaporkan "4 dari 6 customer punya kota yang diketahui" dengan jujur, dan memungkinkan model atau dashboard memperlakukan yang tidak diketahui secara terpisah.

String kosong dan nilai hilang palsu

Tidak semua celah datang sebagai NaN. File export penuh dengan string kosong, "N/A", "-", "none" dan 0 yang dipakai sebagai "tidak diketahui". Ubah dulu menjadi nilai hilang yang sesungguhnya, supaya alat-alat di atas bekerja:

raw = pd.Series(["Jakarta", "", "N/A", " ", "Bandung", "-"])
clean = raw.str.strip().replace({"": np.nan, "N/A": np.nan, "-": np.nan})
print(clean.isna().sum())
4

Waktu terbaik untuk melakukan ini adalah saat membaca file: pd.read_csv(..., na_values=["N/A", "-"]), seperti yang kamu lihat di modul 2.

Mengapa ini penting untuk kode yang bersih

Buat keputusan soal data yang hilang secara eksplisit dan sejak awal, dalam satu langkah pembersihan, dengan komentar yang menjelaskan alasannya: "orders yang hilang berarti nol order: kolom ini hanya diisi kalau ada order". Hindari menyebar pemanggilan fillna(0) di seluruh analisis, di mana setiap pemanggilan diam-diam mengubah arti datanya. Dan jangan pernah membiarkan NaN sampai ke laporan tanpa penjelasan.

Rangkuman

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.