Peta dtype pada chapter sebelumnya menunjukkan dua dunia yang berdampingan: dtypes NumPy (int64, float64) yang tidak mengenal missing, dan keluarga nullable pandas (Int64, Float64, boolean, string) yang menuliskan missing sebagai satu objek tunggal bernama pd.NA. Dunia kedua inilah yang paling sering menyelamatkan pekerjaan data sehari-hari, karena kolom bilangan bulat yang memiliki celah tidak lagi dipaksa menjadi float64. Chapter ini mengoperasikan keluarga nullable secara utuh: karakter logika pd.NA, konversi otomatis lewat convert_dtypes(), konversi manual lewat astype(), dan aritmetika yang menjaga missing tetap terlihat.
pd.NA dan Logika Tiga Keadaan #
pd.NA adalah objek tunggal (pd.NA is pd.NA bernilai True) yang dirancang untuk menjalar pada operasi apa pun. Script berikut mengujinya lebih dulu sebelum masuk ke DataFrame.
import pandas as pd
# pd.NA: penanda missing yang konsisten di semua dtype nullable
print("pd.NA is pd.NA :", pd.NA is pd.NA)
print("pd.NA == pd.NA :", pd.NA == pd.NA)
print("pd.NA == 1 :", pd.NA == 1)
print("pd.NA | True :", pd.NA | True)
print("pd.NA | False :", pd.NA | False)
print()
# convert_dtypes memilih keluarga nullable secara otomatis
df = pd.DataFrame({
"kode": pd.Series([101, 102, 103], dtype="int64"),
"stok": pd.Series([12.0, 7.0, 30.0], dtype="float64"),
"harga": pd.Series([9500.5, 12000.0, 7500.25], dtype="float64"),
"nama": pd.Series(["Budi", "Siti", "Dewi"], dtype="str"),
"aktif": pd.Series([True, None, False], dtype="object"),
})
konv = df.convert_dtypes()
for kolom in df.columns:
print(f"{kolom:6s} {str(df[kolom].dtype):8s} -> {str(konv[kolom].dtype)}")
print()
# astype("Int64"): aman dari int64 dan float bulat
print("float bulat ->", pd.Series([1.0, None, 3.0]).astype("Int64").tolist())
print("str angka ->", pd.Series(["4", None, "6"]).astype("Int64").tolist())
try:
pd.Series([1.5, None, 2.9]).astype("Int64")
except TypeError as e:
print("float desimal ->", type(e).__name__, ":", str(e))
print("round dulu ->", pd.Series([1.5, None, 2.9]).round().astype("Int64").tolist())
print()
# aritmetika nullable: missing menjalar, tipe hasil menyesuaikan
stok = pd.Series([10, None, 7], dtype="Int64")
print("stok :", stok.tolist())
print("stok * 2 :", (stok * 2).tolist(), "| dtype:", str((stok * 2).dtype))
print("stok * 2.5 :", (stok * 2.5).tolist(), "| dtype:", str((stok * 2.5).dtype))
print("stok > 8 :", (stok > 8).tolist(), "| dtype:", str((stok > 8).dtype))
print("fillna(0) :", stok.fillna(0).tolist())
print("sum/mean :", stok.sum(), "/", stok.mean())
semua_na = pd.Series([pd.NA, pd.NA], dtype="Int64")
print("semua NA : sum =", semua_na.sum(), "| min_count=1 =", semua_na.sum(min_count=1))Output yang Dihasilkan:
pd.NA is pd.NA : True
pd.NA == pd.NA : <NA>
pd.NA == 1 : <NA>
pd.NA | True : True
pd.NA | False : <NA>
kode int64 -> Int64
stok float64 -> Int64
harga float64 -> Float64
nama str -> string
aktif object -> boolean
float bulat -> [1, <NA>, 3]
str angka -> [4, <NA>, 6]
float desimal -> TypeError : cannot safely cast non-equivalent object to int64
round dulu -> [2, <NA>, 3]
stok : [10, <NA>, 7]
stok * 2 : [20, <NA>, 14] | dtype: Int64
stok * 2.5 : [25.0, <NA>, 17.5] | dtype: Float64
stok > 8 : [True, <NA>, False] | dtype: boolean
fillna(0) : [10, 0, 7]
sum/mean : 17 / 8.5
semua NA : sum = 0 | min_count=1 = <NA>Mari kita bedah bagian pentingnya:
- Baris 4-8 (perilaku
pd.NA):pd.NA == pd.NAdanpd.NA == 1sama-sama menghasilkan<NA>, bukanFalse. Informasi “nilai ini tidak diketahui” tetap terbawa, berbeda darinanyang perbandingannya selaluFalse. - Baris 7 (kombinasi boolean):
pd.NA | TruebernilaiTruekarena secara logika benar apa pun isi NA-nya, sedangkanpd.NA | Falsetetap<NA>. Pola tiga keadaan (True, False, NA) ini diwarisi keluargaboolean. - Baris 12-21 (convert_dtypes): method
convert_dtypes()memilih dtype nullable yang paling pas untuk tiap kolom — hasilnyaInt64,Float64,string, danboolean. - Baris 25-31 (astype):
astype("Int64")berjalan mulus dariint64, float bulat, bahkan string angka; untuk float desimal pandas menolaknya denganTypeError. - Baris 35-43 (aritmetika): operasi pada
Int64menghasilkanInt64, perkalian dengan2.5naik keFloat64, perbandingan menghasilkanboolean— dan<NA>menjalar ke hasil setiap operasi yang bersentuhan dengannya.
Hasil convert_dtypes untuk Lima Kolom #
Blok kedua output memetakan lima dtype awal ke pasangan nullable-nya. Tabel berikut merangkum aturan yang terlihat dari output tersebut.
| dtype awal | Isi contoh | Hasil convert_dtypes |
|---|---|---|
int64 | 101, 102, 103 | Int64 |
float64 (semua bulat) | 12.0, 7.0, 30.0 | Int64 |
float64 (ada desimal) | 9500.5, 12000.0 | Float64 |
str | Budi, Siti, Dewi | string |
object (bool + None) | True, None, False | boolean |
Aturan float64 menarik dicermati: convert_dtypes() memeriksa isinya, dan bila semua nilai bulat maka kolom dipilihkan Int64. Method ini tidak mengubah nilai apa pun; ia hanya memilih representasi yang lebih tepat. Karena itu convert_dtypes() adalah langkah pembersihan pertama yang praktis setelah memuat data, sebelum Anda menulis astype() manual untuk kasus khusus.
Konversi Manual dengan astype dan Jalan Keluarnya #
astype("Int64") menerima sumber yang “aman”: int64, float yang semua nilainya bulat, dan string angka — output menunjukkan ["4", None, "6"] berubah menjadi [4, <NA>, 6]. Namun pandas menolak pd.Series([1.5, None, 2.9]).astype("Int64") dengan TypeError: cannot safely cast non-equivalent object to int64; pesan ini muncul karena 1.5 tidak dapat diwakili bilangan bulat tanpa kehilangan informasi. Jalan keluarnya terlihat pada baris round dulu: bulatkan lebih dulu dengan round(), baru astype("Int64") menghasilkan [2, <NA>, 3].
Aritmetika Nullable: Missing Menjalar #
Blok terakhir mengoperasikan Series stok [10, <NA>, 7]. Kalikan dengan 2 maka hasilnya Int64; kalikan dengan 2.5 maka pandas naikkan ke Float64 agar 17.5 tetap utuh; bandingkan dengan 8 maka hasilnya boolean berisi [True, <NA>, False]. Baris fillna(0) menunjukkan cara mengganti missing dengan nilai tetap — 0 — sehingga perhitungan lanjutan tidak lagi menyentuh NA.
Dua baris terakhir membahas ringkasan statistik. stok.sum() menghasilkan 17 karena skipna=True adalah nilai default, dan stok.mean() menghasilkan 8.5 dari dua nilai yang tersisa. Yang lebih halus ada di semua NA: sum() biasa mengembalikan 0 (jumlah dari nol elemen dianggap nol), sedangkan sum(min_count=1) baru mengembalikan <NA> karena jumlah elemen yang ikut dihitung kurang dari satu. Parameter min_count memberi kendali penuh atas definisi “cukup data” untuk setiap agregasi.
Dengan pd.NA, convert_dtypes(), dan aturan aritmetikanya, keluarga nullable siap dipakai pada data produksi. Chapter berikutnya memperkenalkan mesin penyimpanan alternatif untuk dtype-dtype yang sama: Apache Arrow, yang bisa dipanggil cukup dengan dtype_backend="pyarrow" atau nama dtype seperti string[pyarrow] — dan menghemat memori secara nyata pada data teks besar.
Referensi resmi untuk chapter ini: pandas.NA , pandas.DataFrame.convert_dtypes , dan user guide Working with nullable integers .