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.
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))[False True False True False True]
3
3Mask 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.
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))True
False
TrueNilai 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.
import numpy as np
nilai = np.array([72, 85, 60, 90, 78, 88])
print(np.mean(nilai >= 80))
print(np.mean(nilai[nilai >= 80]))0.5
87.66666666666667Baris 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.
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)))4
1Kondisi 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.
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))[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.
import numpy as np
nilai = np.array([72, 85, 60, 90, 78, 88])
print(np.sum(nilai >= 80))
print(np.sum(nilai[nilai >= 80]))3
263Baris 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)dannp.count_nonzero(mask)menghitung banyaknya elemen yang memenuhi kondisi. np.mean(mask)menghasilkan proporsi elemen terpilih, sedangkannp.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#
- numpy.count_nonzero — NumPy — Dokumentasi resmi penghitungan elemen yang tidak bernilai nol.
- numpy.sum — NumPy
— Dokumentasi resmi penjumlahan yang menjadi dasar penghitungan elemen
Truepada mask. - numpy.mean — NumPy — Dokumentasi resmi rata-rata yang dipakai untuk proporsi mask.
- NumPy: the absolute basics for beginners — NumPy — Tutorial resmi yang memperkenalkan mask boolean dan agregasi di atasnya.