Idenya
Kamu sudah melihat prices * 1.11: satu angka diterapkan ke setiap elemen. Dan prices * units: dua array dengan shape yang sama, elemen demi elemen. Broadcasting adalah aturan yang menghubungkan keduanya: NumPy bisa menggabungkan array dengan shape berbeda dengan meregangkan yang lebih kecil supaya pas, selama shape-nya kompatibel.
Kedengarannya abstrak, tapi kamu akan terus memakainya: menerapkan harga per bulan ke grid toko × bulan, mengurangkan target setiap toko dari barisnya, membandingkan setiap produk dengan setiap tingkat diskon. Semuanya tanpa satu loop pun.
Datanya
Contoh-contoh di modul ini memakai lagi grid unit terjual, sekarang dengan empat toko (baris) dan enam bulan (kolom):
import numpy as np
stores = ["Jakarta", "Bandung", "Surabaya", "Medan"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
units = np.array([
[120, 135, 150, 160, 155, 170],
[ 80, 85, 90, 88, 95, 100],
[ 95, 92, 105, 110, 108, 115],
[ 40, 45, 43, 50, 52, 55],
])
print(units.shape)
(4, 6)
Skalar: broadcast paling sederhana
Kamu sebenarnya sudah memakai broadcasting tanpa tahu namanya. Mengalikan seluruh grid dengan satu angka, misalnya untuk merencanakan stok dua kali lipat di setiap toko, bisa dilakukan karena NumPy menerapkan angka itu ke setiap sel:
print(units * 2)
[[240 270 300 320 310 340]
[160 170 180 176 190 200]
[190 184 210 220 216 230]
[ 80 90 86 100 104 110]]
Angka tunggal itu "diregangkan" ke shape grid-nya. NumPy sebenarnya tidak membuat salinan; ia hanya memakai ulang nilainya.
Satu baris untuk setiap baris
Harga jual berubah setiap bulan (ribu Rupiah). Revenue per toko per bulan adalah unit × harga bulan itu:
monthly_price = np.array([95, 95, 100, 100, 105, 105]) # shape (6,)
revenue = units * monthly_price
print(revenue)
[[11400 12825 15000 16000 16275 17850]
[ 7600 8075 9000 8800 9975 10500]
[ 9025 8740 10500 11000 11340 12075]
[ 3800 4275 4300 5000 5460 5775]]
units ber-shape (4, 6) dan monthly_price ber-shape (6,). NumPy menyejajarkan shape-nya dari kanan: 6 cocok dengan 6, dan baris harga dipakai ulang untuk masing-masing dari 4 toko.
Satu kolom untuk setiap kolom
Setiap toko punya target bulanan. Untuk mengurangkannya dari barisnya sendiri, target-nya harus berupa kolom, shape (4, 1):
targets = np.array([150, 90, 100, 50]).reshape(4, 1)
print(targets)
print(units - targets)
[[150]
[ 90]
[100]
[ 50]]
[[-30 -15 0 10 5 20]
[-10 -5 0 -2 5 10]
[ -5 -8 5 10 8 15]
[-10 -5 -7 0 2 5]]
Kolom (4, 1) diregangkan ke 6 bulan. Angka positif melampaui target, angka negatif meleset.
Bagaimana kalau kamu lupa menjadikannya kolom?
flat_targets = np.array([150, 90, 100, 50]) # shape (4,)
print(units - flat_targets)
ValueError: operands could not be broadcast together with shapes (4,6) (4,)
Disejajarkan dari kanan, 6 (bulan) bertemu 4 (target): tidak cocok, dan tidak ada yang bernilai 1, jadi NumPy menolak. Error itu adalah hadiah: artinya shape-mu tidak bermakna seperti yang kamu kira.
Aturannya
Bandingkan shape dari kanan, dimensi demi dimensi. Dua dimensi kompatibel kalau sama, atau salah satunya 1 (dimensi yang tidak ada dihitung 1).
| Shape A | Shape B | Hasil | Kenapa |
|---|---|---|---|
(4, 6) | () skalar | (4, 6) | skalar cocok dengan apa saja |
(4, 6) | (6,) | (4, 6) | 6 = 6; tidak ada → 1 → diregangkan ke 4 |
(4, 6) | (4, 1) | (4, 6) | 1 diregangkan ke 6; 4 = 4 |
(4, 1) | (1, 6) | (4, 6) | keduanya diregangkan |
(4, 6) | (4,) | error | 6 ≠ 4 dan tidak ada yang 1 |
Untuk "satu nilai per kolom", pakai shape (columns,). Untuk "satu nilai per baris", pakai shape (rows, 1). Kalau ragu, print .shape di kedua sisi.
Setiap kombinasi: kolom bertemu baris
Saat kolom (n, 1) bertemu baris (m,), broadcasting membangun tabel n × m berisi setiap kombinasi. Tabel harga untuk 3 tingkat diskon × 5 produk:
prices = np.array([185, 320, 95, 65, 110])
discounts = np.array([0.0, 0.1, 0.2]).reshape(3, 1)
table = prices * (1 - discounts)
print(table.shape)
print(table)
(3, 5)
[[185. 320. 95. 65. 110. ]
[166.5 288. 85.5 58.5 99. ]
[148. 256. 76. 52. 88. ]]
Baris 0 adalah harga penuh, baris 1 diskon 10%, baris 2 diskon 20%. Di spreadsheet kamu akan membangun ini dengan referensi campuran $A1; di sini cukup satu baris.
Universal function (ufunc)
Aritmetika bukan satu-satunya yang bekerja pada seluruh array. Fungsi matematika NumPy, seperti akar kuadrat, logaritma, pembulatan, dan nilai mutlak, juga diterapkan ke setiap elemen sekaligus. Fungsi-fungsi ini disebut universal function, atau ufunc:
x = np.array([1.0, 4.0, 9.0, 16.0])
print(np.sqrt(x))
print(np.log10(np.array([10, 100, 1000])))
print(np.round(np.array([1.234, 5.678]), 1))
print(np.abs(np.array([-3, 2, -1])))
[1. 2. 3. 4.]
[1. 2. 3.]
[1.2 5.7]
[3 2 1]
np.maximum dan np.minimum membandingkan dua array (atau sebuah array dan sebuah angka) elemen demi elemen, praktis untuk batas bawah dan batas atas:
gap = units - targets
shortfall = np.maximum(-gap, 0) # seberapa jauh di bawah target, 0 kalau tercapai
print(shortfall)
[[30 15 0 0 0 0]
[10 5 0 2 0 0]
[ 5 8 0 0 0 0]
[10 5 7 0 0 0]]
Jangan tertukar antara np.maximum(a, b) (per elemen antara dua array) dan np.max(a) (satu nilai terbesar), yang dibahas di modul berikutnya.
Pembagian dengan nol
Membagi dengan nol tidak membuat crash: NumPy mengembalikan inf (tak hingga) atau nan (not a number) dan mencetak warning:
growth = np.array([10, 0, 5]) / np.array([2, 0, 0])
print(growth)
RuntimeWarning: divide by zero encountered in divide
RuntimeWarning: invalid value encountered in divide
[ 5. nan inf]
0/0 adalah nan, 5/0 adalah inf. Keduanya menyebar ke perhitungan berikutnya, jadi periksa (np.isnan, np.isinf) atau lindungi penyebutnya dengan np.where.
Mengapa ini penting untuk kode yang bersih
Broadcasting mengganti loop bersarang dengan satu ekspresi yang sesuai dengan matematikanya: units - targets alih-alih dua loop for dengan pencatatan index. Harganya, shape menjadi bagian dari makna kodemu, jadi buat shape itu jelas: beri nama pada kolommu (targets yang di-reshape menjadi (4, 1)), dan print shape saat kamu ragu.
Rangkuman
- Aritmetika antar array bekerja elemen demi elemen; broadcasting meregangkan dimensi berukuran 1 (atau yang tidak ada).
- Bandingkan shape dari kanan; setiap pasangan harus sama atau mengandung 1.
- Satu nilai per kolom: shape
(cols,). Satu per baris: shape(rows, 1). - Kolom
(n, 1)dengan baris(m,)memberi setiap kombinasi(n, m). - Ufunc (
np.sqrt,np.log10,np.abs,np.round,np.maximum...) bekerja per elemen. - Pembagian dengan nol memberi
inf/nan, bukan error.