Chapter Fungsi Universal (ufunc) memperkenalkan ufunc (universal function) sebagai fungsi yang bekerja elemen demi elemen dengan kecepatan C. Yang belum dibahas: ufunc bukan sekadar fungsi — ia adalah objek yang membawa beberapa method siap pakai. np.add.reduce, np.add.accumulate, np.multiply.outer, dan np.add.at adalah method milik ufunc yang sama dengan np.add, masing-masing mengubah pola pemakaiannya.

Bab ini membahas empat method tersebut, lalu menutup dengan dua pembungkus Python untuk menjadikan fungsi buatan sendiri berperilaku seperti ufunc — beserta biaya yang menyertainya.

Metode reduce dan accumulate #

reduce melipat seluruh array menjadi satu nilai dengan operasi berulang, sedangkan accumulate menyimpan hasil pelipatan setiap langkah:

python
import numpy as np

nilai = np.array([4, 7, 2, 9])
print(np.add.reduce(nilai))
print(np.add.accumulate(nilai))
print(np.maximum.reduce(nilai))

Output yang Dihasilkan:

text
22
[ 4 11 13 22]
9

Mari kita bedah:

  1. Baris 4 (np.add.reduce(nilai)): menjumlahkan berantai 4 + 7 + 2 + 9 menjadi 22 — hasil identik dengan nilai.sum().
  2. Baris 5 (np.add.accumulate(nilai)): menjalankan penjumlahan berjalan: 4, lalu 4+7=11, 11+2=13, 13+9=22. Hasilnya identik dengan np.cumsum(nilai).
  3. Baris 6 (np.maximum.reduce(nilai)): setiap ufunc bisa dilipat; maximum melipat perbandingan dua-dua dan menghasilkan nilai terbesar, setara dengan nilai.max().

Nilai method ini bukan sekadar gula sintaksis: setiap ufunc yang tersedia — termasuk ufunc buatan sendiri — otomatis mendapat keempat method bab ini.

Tabel Kombinasi dengan outer #

outer menerapkan ufunc pada seluruh pasangan elemen dua array, menghasilkan matriks kombinasi. Ini cara paling ringkas membuat tabel perkalian, misalnya harga dikali banyaknya unit:

python
import numpy as np

harga = np.array([15000, 20000])
jumlah = np.array([1, 2, 3])
print(np.multiply.outer(harga, jumlah))

Output yang Dihasilkan:

text
[[15000 30000 45000]
 [20000 40000 60000]]

Penjelasan:

  1. Baris 5 (np.multiply.outer(harga, jumlah)): setiap elemen harga dipasangkan dengan setiap elemen jumlah; hasilnya matriks 2 x 3 di mana hasil[i, j] = harga[i] * jumlah[j].
  2. Bentuknya sama dengan broadcasting harga.reshape(2, 1) * jumlah yang dibahas pada Chapter Aturan Broadcasting — outer hanya menuliskannya secara eksplisit.
  3. outer bekerja pada ufunc apa pun: np.subtract.outer menghasilkan tabel selisih, np.greater.outer menghasilkan tabel boolean.

add.at untuk Indeks yang Berulang #

Method at melakukan operasi tanpa buffering — penting saat indeks target berulang. Bandingkan dengan fancy indexing biasa yang memanggil operasi sekali per langkah:

python
import numpy as np

saldo = np.array([100, 100, 100])
idx = np.array([0, 1, 0])
np.add.at(saldo, idx, 50)
print(saldo)
saldo2 = np.array([100, 100, 100])
saldo2[idx] += 50
print(saldo2)

Output yang Dihasilkan:

text
[200 150 100]
[150 150 100]

Bedah baris demi baris:

  1. Baris 5 (np.add.at(saldo, idx, 50)): add.at menambahkan 50 sebanyak tiga kali berturut-turut ke indeks 0, 1, 0. Indeks 0 dikenai dua penambahan, sehingga menjadi 200.
  2. Baris 8 (saldo2[idx] += 50): fancy indexing yang dibahas pada Chapter Fancy Indexing membaca saldo2[idx] sebagai salinan sementara, menambahkan 50 pada salinan itu, lalu menulis balik satu kali. Penambahan kedua ke indeks 0 menimpa yang pertama, bukan menumpuk.
  3. Hasilnya 150 pada indeks 0 — berbeda dari 200 versi at. Untuk agregasi berbasis indeks yang berulang, at adalah method yang benar.

Membungkus Fungsi Python: frompyfunc dan vectorize #

Fungsi Python biasa dapat dijadikan “ufunc” dengan np.frompyfunc atau np.vectorize. Keduanya menjalankan fungsi Python per elemen — kenyamanan meningkat, kecepatan tidak:

python
import numpy as np

def klasifikasi(x):
    return "lunas" if x >= 0 else "hutang"

vf = np.frompyfunc(klasifikasi, 1, 1)
a1 = vf(np.array([-1, 0, 5]))
print(a1, a1.dtype)
vv = np.vectorize(klasifikasi)
a2 = vv(np.array([-1, 0, 5]))
print(a2, a2.dtype)

Output yang Dihasilkan:

text
['hutang' 'lunas' 'lunas'] object
['hutang' 'lunas' 'lunas'] <U6

Penjelasan tiap bagian:

  1. Baris 6 (np.frompyfunc(klasifikasi, 1, 1)): dua angka menyatakan jumlah input dan output fungsi. Hasilnya berdtype object — setiap elemen adalah objek string Python biasa.
  2. Baris 9 (np.vectorize(klasifikasi)): tipe output disimpulkan dari pemanggilan pertama, sehingga hasilnya array string Unicode <U6 yang lebih nyaman dipakai.
  3. Argumen otypes pada np.vectorize dapat memaksa tipe output secara eksplisit bila penyimpulan otomatis tidak diinginkan.

Ringkasan method ufunc:

MethodPeranPadanan lazim
reducelipat semua elemen jadi satu nilaisum(), max()
accumulatesimpan hasil pelipatan tiap langkahnp.cumsum
outersemua pasangan elemen dua arraybroadcasting manual
atoperasi di indeks tanpa bufferinga[idx] += ... (berisiko jika indeks berulang)

Kapan Butuh apply_along_axis #

frompyfunc dan vectorize di atas membungkus fungsi per elemen. Kadang fungsi kita justru membutuhkan satu baris atau satu kolom utuh — misalnya menghitung rentang (max − min) tiap baris. Untuk pola itu NumPy menyediakan np.apply_along_axis: fungsi Python biasa diterapkan pada setiap potongan 1D sepanjang axis tertentu, lalu hasil tiap potongan disusun menjadi array baru:

python
import numpy as np

def rentang(baris):
    return baris.max() - baris.min()

def normalisasi(baris):
    return (baris - baris.min()) / (baris.max() - baris.min())

penjualan = np.array([[120, 300, 250], [80, 90, 400], [200, 350, 150]])
print(np.apply_along_axis(rentang, 1, penjualan))
print(np.apply_along_axis(normalisasi, 1, penjualan).round(3))
print(np.apply_along_axis(rentang, 0, penjualan))
print(np.ptp(penjualan, axis=1))

Output yang Dihasilkan:

text
[180 320 200]
[[0.    1.    0.722]
 [0.    0.031 1.   ]
 [0.25  1.    0.   ]]
[120 260 250]
[180 320 200]

Penjelasan:

  1. Baris 10-11 (argumen axis=1): fungsi menerima satu baris utuh per pemanggilan; rentang mengembalikan satu angka per baris, sedangkan normalisasi mengembalikan array sepanjang baris sehingga bentuk asli array kembali.
  2. Baris 12 (argumen axis=0): potongan yang diteruskan kini berupa kolom, sehingga rentang dihitung antartoko untuk setiap produk.
  3. Baris 13 (np.ptp): rentang max − min ternyata memiliki padanan ufunc bawaan; hasilnya identik dengan versi apply_along_axis.

Catatan performa: np.apply_along_axis adalah loop Python terselubung — fungsi kita dipanggil sekali per potongan, tanpa kecepatan C milik ufunc. Karena itu, cari dulu alternatif vektor penuh seperti np.ptp pada baris terakhir contoh; gunakan apply_along_axis hanya untuk fungsi yang memang tidak dapat ditulis sebagai operasi array.