Dalam pekerjaan data sehari-hari, kita jarang mendapat array yang isinya rapi semua. Sensor bisa gagal membaca, sel di file CSV bisa kosong, dan operasi tertentu bisa menghasilkan bilangan yang tidak terdefinisi. NumPy mewakili dua keadaan istimewa itu dengan konstanta np.nan (Not a Number) untuk nilai yang tidak terdefinisi, serta np.inf untuk nilai yang melampaui jangkauan float, seperti hasil pembagian dengan nol.

Bab ini membahas dari mana kedua konstanta itu berasal, bagaimana interpreter menanganinya, dan satu perilaku yang paling sering mengejutkan pemula: satu nan saja bisa membuat seluruh hasil agregasi ikut menjadi nan. Setelah pola ini terlihat, bab-bab berikutnya — dari pemeriksaan hingga filtering — akan terasa sebagai jawaban langsung atas masalah ini.

Konstanta nan dan inf #

np.nan, np.inf, dan negasinya -np.inf dapat ditulis langsung sebagai elemen array. Keduanya adalah nilai float biasa, sehingga array penampungnya harus bertipe float:

python
import numpy as np

pembacaan = np.array([1.0, np.nan, np.inf, -np.inf, 27.5])
print(pembacaan)
print(type(np.nan), type(np.inf))

Output yang Dihasilkan:

text
[ 1.   nan  inf -inf 27.5]
<class 'float'> <class 'float'>

Mari kita bedah:

  1. Baris 3 (np.array(...)): interpreter menyusun array float64 berisi lima elemen; np.nan, np.inf, dan -np.inf disimpan sebagai pola bit float khusus, bukan string.
  2. Baris 4 (print(pembacaan)): print menampilkan konstanta itu secara ringkas sebagai nan, inf, dan -inf di antara elemen biasa.
  3. Baris 5 (type(...)): np.nan dan np.inf ternyata float Python biasa — keduanya juga bisa dipakai tanpa NumPy, misalnya lewat float("nan").

Asal-usul: Pembagian dengan Nol #

Nilai nan dan inf tidak selalu ditulis manual; sering kali keduanya lahir dari perhitungan. Di sinilah NumPy berbeda dari Python biasa: Python memunculkan error untuk pembagian nol, sedangkan NumPy mengembalikan nilai khusus dan hanya memberi peringatan.

python
import numpy as np

try:
    hasil = 1 / 0
except ZeroDivisionError as e:
    print("Python biasa -> ZeroDivisionError:", e)

pembilang = np.array([1.0, 0.0])
penyebut = np.array([0.0, 0.0])
with np.errstate(divide="ignore", invalid="ignore"):
    hasil = pembilang / penyebut
print("NumPy ->", hasil)

Output yang Dihasilkan:

text
Python biasa -> ZeroDivisionError: division by zero
NumPy -> [inf nan]

Penjelasan tiap bagian:

  1. Baris 3-6: pembagian bilangan Python 1 / 0 memunculkan ZeroDivisionError, karena itu blok ini dibungkus try/except agar script tetap berjalan.
  2. Baris 8-9: dua array float64 disiapkan; pembagian dievaluasi elemen demi elemen, sehingga 1.0 / 0.0 dan 0.0 / 0.0 menghasilkan nilai yang berbeda.
  3. Baris 10 (np.errstate(...)): konteks ini meredam peringatan floating-point di dalam blok with saja — divide="ignore" untuk pembagian nol, invalid="ignore" untuk hasil tak terdefinisi.
  4. Baris 11: 1.0 / 0.0 menghasilkan inf, sedangkan 0.0 / 0.0 menghasilkan nan; keduanya dikembalikan tanpa error.
OperasiHasilAlasan
1.0 / 0.0infhasil menuju tak hingga positif
-1.0 / 0.0-infhasil menuju tak hingga negatif
0.0 / 0.0nantidak terdefinisi secara matematis
np.inf - np.infnantak hingga dikurangi tak hingga tidak punya jawaban
1.0 / np.inf0.0hasil menuju nol

Peringatan yang Muncul di Balik Layar #

Tanpa np.errstate, operasi di atas tetap berhasil tetapi mencetak RuntimeWarning ke stderr. Kita dapat membuktikan isi peringatan itu dengan modul warnings, sehingga pesan tersebut menjadi output yang teruji:

python
import numpy as np
import warnings

pembilang = np.array([1.0, 0.0])
penyebut = np.array([0.0, 0.0])
with warnings.catch_warnings(record=True) as terekam:
    warnings.simplefilter("always")
    hasil = pembilang / penyebut
for w in terekam:
    print(w.category.__name__, "->", w.message)
print(hasil)

Output yang Dihasilkan:

text
RuntimeWarning -> divide by zero encountered in divide
RuntimeWarning -> invalid value encountered in divide
[inf nan]

Mari kita telaah:

  1. Baris 6-8: warnings.catch_warnings(record=True) mencegat peringatan ke dalam list tererekam, sehingga pesan peringatan bisa dicetak sebagai output yang pasti.
  2. Baris 9-10: dua peringatan tercatat — pembagian nol untuk elemen 1.0 / 0.0, dan invalid untuk 0.0 / 0.0.
  3. Baris 11: hasilnya tetap [inf nan]; peringatan tidak pernah mengubah nilai, hanya memberi tanda agar kita memeriksanya.

nan Tidak Sama dengan Apa Pun #

Perilaku paling penting dari nan: perbandingannya dengan apa pun — termasuk dengan nan itu sendiri — selalu False. Konsekuensinya, uji kesamaan biasa tidak bisa dipakai untuk mendeteksi nan, dan satu nan menular ke seluruh hasil agregasi:

python
import numpy as np

a = np.array([1.0, np.nan, 3.0])
print("nan == nan ->", np.nan == np.nan)
print("nan != nan ->", np.nan != np.nan)
print("a == nan   ->", a == np.nan)
print("a.sum()    ->", a.sum())
print("a.mean()   ->", a.mean())

Output yang Dihasilkan:

text
nan == nan -> False
nan != nan -> True
a == nan   -> [False False False]
a.sum()    -> nan
a.mean()   -> nan

Mari kita baca hasilnya:

  1. Baris 4-5: nan == nan bernilai False dan nan != nan bernilai True — kebalikan dari intuisi kita tentang bilangan biasa.
  2. Baris 6 (a == np.nan): perbandingan per elemen menghasilkan False semua, sehingga pola a[a == nan] tidak akan pernah menemukan satu pun elemen.
  3. Baris 7-8: sum dan mean ikut menjadi nan tanpa pesan error — satu elemen hilang cukup untuk meracuni seluruh statistik.

Perilaku inf dalam Aritmetika #

Berbeda dari nan, inf masih mengikuti sebagian aturan aritmetika. Namun kombinasi tertentu justru menghasilkan nan karena jawabannya tidak terdefinisi:

python
import numpy as np

x = np.array([np.inf])
with np.errstate(invalid="ignore"):
    print("inf - inf :", (x - x)[0])
    print("inf * 0   :", (x * 0.0)[0])
print("inf + 1000:", np.inf + 1000.0)
print("1 / inf   :", 1.0 / np.inf)

Output yang Dihasilkan:

text
inf - inf : nan
inf * 0   : nan
inf + 1000: inf
1 / inf   : 0.0

Penjelasan singkat:

  1. Baris 3 (x = np.array([np.inf])): array berisi satu elemen inf; pengurangan dan perkaliannya dieksekusi sebagai operasi array.
  2. Baris 4-6: inf - inf dan inf * 0 menghasilkan nan; kedua operasi memicu peringatan invalid, maka blok ini dibungkus np.errstate(invalid="ignore") agar output tetap bersih.
  3. Baris 7-8: inf + 1000 tetap inf, sedangkan 1 / inf menuju 0.0 — dua hasil yang masuk akal secara matematis.

Pola praktisnya: inf menandai hasil yang “terlalu besar”, nan menandai hasil yang tidak bisa ditentukan. Keduanya sama-sama harus dideteksi sebelum data dipakai menghitung, dan itulah topik bab berikutnya.