Agregasi bersyarat

Mengagregasi dengan mask boolean dan menghitung nilai nonzero.

Daftar isi

Agregasi bersyarat menghitung statistik hanya atas elemen yang memenuhi kondisi tertentu, misalnya banyaknya nilai ujian di atas 80 atau proporsi pembacaan sensor yang melewati ambang batas. Topik ini membahas cara menghitung dan mengagregasi elemen terpilih dengan memadukan fungsi reduksi dan mask boolean, yaitu array bertipe bool yang menandai posisi elemen terpilih. Pembahasan mencakup penghitungan dengan sum dan count_nonzero, proporsi dan rata-rata bersyarat, kondisi gabungan, serta agregasi bersyarat per sumbu pada array dua dimensi.

Menghitung elemen yang memenuhi kondisi#

Cara paling langsung untuk menghitung elemen yang memenuhi kondisi adalah menjumlahkan mask-nya. Tipe bool pada NumPy diperlakukan sebagai bilangan, dengan True bernilai 1 dan False bernilai 0, sehingga np.sum pada mask menghasilkan banyaknya elemen True. Fungsi np.count_nonzero melakukan hal yang sama pada mask, yaitu menghitung elemen yang tidak bernilai nol, dan True termasuk yang tidak nol.

python Contoh: menghitung elemen yang memenuhi kondisi
import numpy as np

nilai = np.array([72, 85, 60, 90, 78, 88])
mask = nilai >= 80
print(mask)
print(np.sum(mask))
print(np.count_nonzero(mask))
text Keluaran
[False  True False  True False  True]
3
3

Mask menandai tiga nilai yang berjumlah 80 atau lebih, yaitu 85, 90, dan 88, sehingga kedua cara menghasilkan 3. Kedua bentuk tersebut juga dapat ditulis langsung tanpa variabel perantara, misalnya np.sum(nilai >= 80) atau np.count_nonzero(nilai >= 80), dan hasilnya sama persis. Bentuk metode juga sah, sehingga mask.sum() ekuivalen dengan np.sum(mask).

Selain menghitung banyaknya elemen terpilih, NumPy menyediakan dua reduksi bersyarat yang hasilnya berupa satu nilai kebenaran. Fungsi np.any menghasilkan True apabila minimal satu elemen memenuhi kondisi, sedangkan np.all menghasilkan True apabila seluruh elemen memenuhinya. Keduanya menjawab pertanyaan keberadaan dan keumuman, misalnya apakah ada pembacaan sensor yang melewati batas atau apakah semua nilai berada di dalam rentang.

python Contoh: any dan all sebagai reduksi kebenaran
import numpy as np

nilai = np.array([72, 85, 60, 90, 78, 88])
print(np.any(nilai > 89))
print(np.all(nilai > 89))
print(np.all(nilai >= 60))
text Keluaran
True
False
True

Nilai 90 membuat np.any(nilai > 89) menghasilkan True, sedangkan np.all(nilai > 89) menghasilkan False karena tidak semua nilai melewati 89. Pemanggilan terakhir menghasilkan True karena nilai terkecilnya memang 60. Ketiga pemanggilan mengembalikan satu nilai bool untuk seluruh array.

Proporsi dan rata-rata elemen terpilih#

Penerapan np.mean pada mask menghasilkan proporsi elemen True, bukan rata-rata nilai datanya. Karena True dihitung 1 dan False dihitung 0, rata-rata mask sama dengan banyaknya elemen terpilih dibagi banyaknya seluruh elemen, sehingga hasilnya selalu berada di antara 0 dan 1. Adapun rata-rata nilai elemen terpilih diperoleh dengan menyaring lebih dahulu melalui a[mask], lalu menghitung mean dari hasil penyaringannya.

python Contoh: proporsi mask dan rata-rata nilai terpilih
import numpy as np

nilai = np.array([72, 85, 60, 90, 78, 88])
print(np.mean(nilai >= 80))
print(np.mean(nilai[nilai >= 80]))
text Keluaran
0.5
87.66666666666667

Baris pertama menghasilkan 0,5 karena tiga dari enam nilai berada di atas atau sama dengan 80, sedangkan baris kedua menghasilkan sekitar 87,67, yaitu rata-rata dari 85, 90, dan 88. Kedua angka itu menjawab pertanyaan yang berbeda, sehingga penulisan a[mask] di dalam atau di luar mean harus dipilih sesuai pertanyaan yang ingin dijawab. Perbedaan yang sifatnya sama juga berlaku pada sum, yaitu antara np.sum(mask) yang menghitung banyaknya elemen terpilih dan np.sum(a[mask]) yang menjumlahkan nilai elemen terpilih. Perbandingan kedua bentuk tersebut ditunjukkan pada bagian Kesalahan umum.

Kondisi yang lebih rumit disusun dengan operator & untuk dan, | untuk atau, serta ~ untuk pembalikan, dengan setiap perbandingan dibungkus tanda kurung. Aturan penulisan ini sama dengan aturan pada mask pada umumnya, dan hasil gabungannya tetap berupa mask yang siap dihitung.

python Contoh: menghitung dengan kondisi gabungan
import numpy as np

nilai = np.array([72, 85, 60, 90, 78, 88])
kondisi = (nilai >= 70) & (nilai <= 89)
print(np.count_nonzero(kondisi))
print(np.count_nonzero((nilai < 60) | (nilai > 89)))
text Keluaran
4
1

Kondisi pertama menangkap nilai yang berada di rentang 70 sampai 89, yaitu 72, 85, 78, dan 88, sehingga hasilnya 4. Kondisi kedua menangkap nilai di luar rentang 60 sampai 89 pada kedua sisinya, dan hanya 90 yang memenuhinya, sehingga hasilnya 1.

Agregasi bersyarat per sumbu#

Pada array dua dimensi, mask boolean dapat diagregasikan per sumbu sebagaimana fungsi reduksi biasa. Hasil np.sum pada mask dengan axis=1 adalah banyaknya elemen terpilih di setiap baris, sedangkan np.mean pada mask dengan axis=0 adalah proporsi elemen terpilih di setiap kolom. Bentuk hasilnya mengikuti aturan yang sama, yaitu sumbu yang diringkas hilang dari hasil.

python Contoh: agregasi bersyarat per sumbu pada array dua dimensi
import numpy as np

skor = np.array([[80, 92, 78, 88], [85, 74, 90, 79], [70, 82, 76, 84]])
print(np.sum(skor >= 80, axis=1))
print(np.mean(skor >= 80, axis=0))
text Keluaran
[3 2 2]
[0.66666667 0.66666667 0.33333333 0.66666667]

Baris pertama memuat banyaknya nilai yang mencapai 80 pada masing-masing siswa, yaitu tiga, dua, dan dua. Baris kedua memuat proporsi siswa yang mencapai 80 pada masing-masing mapel, misalnya 0,67 pada mapel pertama karena dua dari tiga siswa memenuhinya. Pola ini bekerja untuk fungsi reduksi lain pula, sehingga penghitungan bersyarat per baris maupun per kolom tidak memerlukan perulangan eksplisit.

Agregasi berlapis juga dapat disusun dari hasil semacam itu. Nilai terbesar dari penghitungan per baris, misalnya, diperoleh dengan menyusun np.sum(skor >= 80, axis=1) terlebih dahulu, lalu menerapkan max pada hasilnya. Pola dua tahap seperti ini lazim dipakai untuk pertanyaan majemuk, misalnya mencari siswa dengan paling banyak nilai di atas ambang.

Kesalahan umum#

Kesalahan pertama adalah menukar np.sum(mask) dengan np.sum(a[mask]). Bentuk pertama menghitung banyaknya elemen yang terpilih, sedangkan bentuk kedua menjumlahkan nilai elemen yang terpilih. Keduanya sah, tetapi menjawab pertanyaan yang berbeda, dan tertukarnya keduanya menghasilkan laporan yang menyesatkan.

python Contoh: menghitung elemen terpilih versus menjumlahkan nilainya
import numpy as np

nilai = np.array([72, 85, 60, 90, 78, 88])
print(np.sum(nilai >= 80))
print(np.sum(nilai[nilai >= 80]))
text Keluaran
3
263

Baris pertama menyatakan ada tiga nilai yang memenuhi kondisi, sedangkan baris kedua menyatakan total nilai ketiganya adalah 263. Apabila tujuannya menghitung banyaknya elemen, agregasi dilakukan pada mask langsung, dan apabila tujuannya menjumlahkan nilainya, mask dipakai sebagai indeks terlebih dahulu.

Kesalahan kedua adalah menggabungkan kondisi tanpa tanda kurung atau dengan and serta or bawaan Python. Penulisan semacam itu memicu ValueError atau menghasilkan kondisi yang tidak sesuai maksud, karena operator & dikerjakan lebih dahulu daripada perbandingan. Perbaikannya adalah membungkus setiap perbandingan dengan tanda kurung dan memakai &, |, serta ~ sebagaimana contoh pada bagian proporsi di atas.

Ringkasan#

  • Mask boolean diperlakukan sebagai 1 dan 0, sehingga np.sum(mask) dan np.count_nonzero(mask) menghitung banyaknya elemen yang memenuhi kondisi.
  • np.mean(mask) menghasilkan proporsi elemen terpilih, sedangkan np.mean(a[mask]) menghasilkan rata-rata nilai elemen terpilih.
  • Kondisi gabungan disusun dengan &, |, dan ~, dengan setiap perbandingan dibungkus tanda kurung.
  • Mask pada array dua dimensi dapat diagregasikan per sumbu, sehingga banyaknya atau proporsi elemen terpilih per baris dan per kolom dihitung tanpa perulangan.

Referensi#