Masking boolean adalah teknik pemilihan elemen array berdasarkan kondisi, bukan berdasarkan posisi indeks. Alat utamanya adalah mask, yakni array bertipe bool yang panjangnya sama dengan array sumber dan berisi True pada posisi yang memenuhi kondisi. Topik ini membahas cara membuat mask, menyaring dan mengganti nilai dengan mask, menggabungkan beberapa kondisi, serta satu sifat pentingnya, yaitu hasil penyaringan selalu berupa salinan baru.
Mask boolean dari operasi perbandingan#
Mask dibuat dengan menerapkan operasi perbandingan pada array, misalnya a > 10. Operasi tersebut dikerjakan elemen demi elemen sehingga hasilnya adalah array bool dengan panjang yang sama, berisi True pada posisi yang memenuhi kondisi dan False pada posisi yang tidak. Mask tidak menyimpan nilai data, melainkan hanya tanda pilih pada setiap posisi.
import numpy as np
a = np.array([12, 7, 25, 3, 18, 9])
mask = a > 10
print(mask)
print(mask.dtype)[ True False True False True False]
boolNilai 12, 25, dan 18 memenuhi kondisi > 10, sehingga True muncul pada indeks 0, 2, dan 4. Tipe hasilnya adalah bool dan panjangnya enam, sama persis dengan panjang a. Setiap elemen bool hanya menempati satu byte, sehingga mask array berukuran besar tetap ringan disimpan.
Mask tidak terbatas pada satu jenis perbandingan, karena operator >, <, >=, <=, ==, dan != semuanya menghasilkan mask dengan aturan yang sama. Mask dari perbandingan dua array juga dibentuk dengan aturan tersebut, dengan syarat bentuk keduanya selaras. Apabila panjang mask tidak cocok dengan array yang diindeks, pengindeksan memunculkan error IndexError, sehingga mask hampir selalu dibuat dari array yang sama dengan array sumbernya.
Menyaring dan mengganti nilai#
Mask dipakai sebagai indeks untuk mengambil elemen terpilih, ditulis a[mask], dan dipakai pula untuk mengganti nilai melalui penugasan a[mask] = pengganti. Kedua bentuk tersebut dapat ditulis langsung dengan ungkapan perbandingannya tanpa menyimpan mask pada variabel terpisah, misalnya a[a > 10]. Penugasan dengan mask menggantikan perulangan berindeks yang panjangnya bergantung pada kondisi, dan bentuk inilah idiom penggantian bersyarat pada NumPy.
import numpy as np
a = np.array([12, 7, 25, 3, 18, 9])
print(a[a > 10])
a[a > 10] = 0
print(a)[12 25 18]
[0 7 0 3 0 9]Pemanggilan pertama mengambil tiga elemen yang memenuhi kondisi, sedangkan penugasan kedua mengganti ketiganya menjadi 0 dalam satu baris tanpa perulangan. Pola penugasan seperti ini lazim digunakan untuk menetralkan nilai tak wajar, misalnya pembacaan sensor suhu yang melewati batas atas diganti dengan nilai default. Seluruh penggantian terjadi pada array asal karena penugasan dengan mask merupakan bentuk penulisan, bukan pembacaan. Apabila penggantinya bukan satu nilai tetap, misalnya rata-rata elemen yang lolos kondisi, mask yang sama dipakai dua kali, yaitu sekali untuk menghitung dan sekali untuk menugaskan. Pemisahan dua langkah semacam ini membuat perhitungan dan penulisannya dapat diperiksa secara terpisah.
Menggabungkan beberapa mask#
Beberapa kondisi digabungkan dengan operator & untuk dan, | untuk atau, serta ~ untuk pembalikan. Ketiganya bekerja elemen demi elemen pada array bool, sehingga hasil gabungannya juga berupa mask. Operator logika Python and, or, dan not tidak digunakan karena tidak bekerja elemen demi elemen pada array.
Setiap kondisi wajib dibungkus tanda kurung. Operator & memiliki urutan pengerjaan yang lebih tinggi daripada operator perbandingan, sehingga penulisan a > 5 & a < 20 tidak dibaca sebagaimana tertulis. Dengan tanda kurung, urutan pengerjaan menjadi jelas dan kondisi dibaca sebagai (a > 5) & (a < 20).
import numpy as np
a = np.array([12, 7, 25, 3, 18, 9])
rendah = a < 10
genap = a % 2 == 0
print(a[rendah | genap])
print(a[(a > 5) & (a < 20)])
print(a[~rendah])[12 7 3 18 9]
[12 7 18 9]
[12 25 18]Gabungan rendah | genap menangkap nilai yang kurang dari 10 atau berupa bilangan genap, sehingga hasilnya [12 7 3 18 9]. Irisan (a > 5) & (a < 20) menangkap nilai yang berada di antara 6 sampai 19, dan pembalikan ~rendah menangkap semua nilai yang tidak kurang dari 10. Ketiganya menunjukkan bahwa mask dapat disusun menjadi kondisi yang kompleks tanpa perulangan eksplisit.
Mask menghasilkan salinan#
Pengambilan elemen dengan mask menghasilkan salinan baru, bukan view. Salinan dibuat karena jumlah dan posisi elemen terpilih tidak beraturan, sehingga elemen hasil penyaringan tidak dapat berbagi blok memori dengan array asalnya. Akibatnya, mengubah isi hasil penyaringan tidak berdampak apa pun pada array asalnya. Pola pemeriksaannya sama dengan yang dipakai pada view, yaitu np.shares_memory, sehingga alat yang satu ini cukup diingat untuk kedua arah.
import numpy as np
a = np.array([12, 7, 25, 3, 18, 9])
terpilih = a[a > 10]
print(np.shares_memory(a, terpilih))
terpilih[0] = 99
print(a)False
[12 7 25 3 18 9]Fungsi np.shares_memory mengembalikan False, dan perubahan terpilih[0] = 99 tidak memengaruhi a. Perilaku ini berkebalikan dengan slicing yang menghasilkan view. Jika hasil penyaringan memang perlu dituliskan kembali ke array asal, gunakan penugasan dengan mask, misalnya a[a > 10] = nilai_baru, sebagaimana ditunjukkan pada bagian Menyaring dan mengganti nilai. Konsekuensinya terasa pada array besar, karena setiap penyaringan membayar satu salinan penuh meskipun yang terpilih hanya sebagian kecil elemennya. Apabila penyaringan dilakukan berulang kali pada data yang sama, menyimpan indeks elemen terpilih dan memakainya kembali lebih hemat daripada mengulang pembuatan salinan.
Kesalahan umum#
- Menggunakan
andatauoruntuk menggabungkan kondisi. Python kemudian mencoba menilai kebenaran seluruh array sekaligus dan memunculkanValueErrorsebagaimana kode di bawah. Perbaikannya adalah menggunakan&dan|yang bekerja elemen demi elemen. - Menghilangkan tanda kurung pada gabungan kondisi. Karena
&dikerjakan lebih dahulu daripada perbandingan, penulisan tanpa kurung tidak membentuk kondisi yang dimaksud. Perbaikannya adalah membungkus setiap perbandingan dengan tanda kurung secara konsisten. - Mengira
b = a[mask]adalah view yang terikat padaa. Hasilnya justru salinan, sehingga mutasi padabtidak terbawa kea, berkebalikan dengan harapan. Perbaikannya adalah menuliskan kembali ke array asal melalui penugasan dengan mask bila efeknya memang diinginkan.
import numpy as np
a = np.array([12, 7, 25, 3, 18, 9])
try:
print(a[a > 5 & a < 20])
except ValueError as e:
print(f"ValueError: {e}")
try:
print(a[a > 5 and a < 20])
except ValueError as e:
print(f"ValueError: {e}")ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()Kedua penulisan memunculkan error yang sama. Bentuk pertama gagal karena urutan pengerjaan & membuat perbandingan menjadi berantai, sedangkan bentuk kedua gagal karena and menilai kebenaran seluruh array. Pesan ValueError pada kedua kasus menyarankan pemakaian a.any() atau a.all(), yang dipakai apabila kebenaran gabungan seluruh elemen ingin diketahui.
Ringkasan#
- Operasi perbandingan pada array menghasilkan mask, yaitu array
boolyang menandai posisi elemen terpilih. a[mask]mengambil elemen terpilih, sedangkana[mask] = penggantimenggantinya langsung pada array asal.- Kondisi berganda digabung dengan
&,|, dan~, dengan setiap kondisi dibungkus tanda kurung agar urutan pengerjaan benar. - Pengambilan elemen dengan mask menghasilkan salinan baru, sehingga mutasi pada hasilnya tidak menyentuh array asal.
Referensi#
- Indexing on ndarrays — NumPy — Panduan resmi yang membahas penyaringan boolean dan penggabungan kondisi.
- Indexing routines — NumPy — Rujukan teknis pengindeksan boolean pada ndarray beserta sifat hasil salinannya.