Sebuah jaringan toko menjual empat produk melalui tiga cabang. Harga produk di tiap cabang tersimpan rapi sebagai matriks berbentuk (3, 4) — tiga cabang, empat produk. Lalu kebijakan potongan diumumkan: tiap produk mendapat potongan berbeda, jadi data potongan hanya berisi empat angka, berbentuk (4,). Bagaimana cara mengurangkan array berisi dua belas elemen dengan array berisi empat elemen?

NumPy menjawabnya dengan broadcasting: mekanisme yang mengoperasikan array berbentuk berbeda seolah-olah bentuknya sama, tanpa menduplikasi data kecil. NumPy tidak menyalin potongan menjadi matriks penuh; potongan empat angka itu “tampak” sebesar matriks harga pada saat operasi berjalan.

Bab ini memperkenalkan broadcasting lewat tiga bukti:

  • Mengurangi matriks harga dengan potongan per produk berbentuk (4,).
  • Mengurangi matriks harga dengan potongan per cabang berbentuk (3, 1).
  • Memeriksa memori untuk membuktikan bahwa perluasan terjadi tanpa penyalinan data.

Matriks harga dan potongan per produk #

Kode berikut mengurangi matriks (3, 4) dengan vektor (4,) dalam satu ekspresi:

python
import numpy as np

harga = np.array([[12000, 15000, 10000, 8000],
                  [13000, 14500, 11000, 9000],
                  [12500, 15500, 10500, 8500]])
potongan = np.array([500, 1000, 250, 0])

hasil = harga - potongan
print(hasil)
print(hasil.shape)

Output yang Dihasilkan:

text
[[11500 14000  9750  8000]
 [12500 13500 10750  9000]
 [12000 14500 10250  8500]]
(3, 4)

Mari kita bedah apa yang dilakukan interpreter pada tiap baris:

  1. Baris 3-5 (harga): np.array membentuk matriks 3 × 4 — baris untuk cabang, kolom untuk produk, satuan rupiah.
  2. Baris 6 (potongan): array 1-D berbentuk (4,); tiap angka adalah potongan untuk satu produk dan berlaku di semua cabang.
  3. Baris 8 (harga - potongan): NumPy menyelaraskan (3, 4) dengan (4,), memperlakukan potongan seolah berbentuk (1, 4), lalu memperluasnya menjadi tiga baris secara virtual. Pengurangan per elemen kemudian berjalan pada bentuk (3, 4).
  4. Baris 10 (hasil.shape): hasil berbentuk (3, 4) — operasi antar bentuk berbeda menghasilkan array utuh tanpa satu pun loop eksplisit.

Perhatikan arah alirnya: vektor (4,) berbaris sejajar dengan kolom, sehingga nilainya “mengalir ke bawah” ke seluruh baris. Kolom pertama matriks dikurangi 500, kolom kedua dikurangi 1000, dan seterusnya.

Potongan per cabang #

Skenario kedua membalik arah: potongan berbeda per cabang, sama untuk semua produk. Data potongan ditulis sebagai array (3, 1):

python
import numpy as np

harga = np.array([[12000, 15000, 10000, 8000],
                  [13000, 14500, 11000, 9000],
                  [12500, 15500, 10500, 8500]])
potongan_cabang = np.array([[1000], [750], [1250]])

hasil = harga - potongan_cabang
print(hasil)
print(hasil.shape)

Output yang Dihasilkan:

text
[[11000 14000  9000  7000]
 [12250 13750 10250  8250]
 [11250 14250  9250  7250]]
(3, 4)
  1. Baris 6 (potongan_cabang): nested list menghasilkan array berbentuk (3, 1) — tiga baris, satu kolom.
  2. Baris 8 (harga - potongan_cabang): kolom tunggal diperluas menyamping menjadi empat kolom; tiap baris matriks dikurangi potongan cabangnya sendiri.
  3. Baris 9-10: hasil tetap (3, 4); arah perluasan berbeda, bentuk hasil sama.

Tabel berikut merangkum peran tiap array pada kedua skenario:

ArrayBentukPeran pada data toko
harga(3, 4)harga tiap produk di tiap cabang
potongan(4,)potongan per produk, sama di semua cabang
potongan_cabang(3, 1)potongan per cabang, sama di semua produk
hasil(3, 4)harga setelah potongan

Perluasan terjadi tanpa penyalinan #

Klaim “tanpa menduplikasi data” dapat kita buktikan dengan np.broadcast_to, fungsi yang memperlihatkan bentuk hasil perluasan secara eksplisit:

python
import numpy as np

potongan = np.array([500, 1000, 250, 0])
diperluas = np.broadcast_to(potongan, (3, 4))

print(diperluas.shape)
print(diperluas.strides)
print(diperluas.base is potongan)
print(potongan.nbytes, diperluas.nbytes)

Output yang Dihasilkan:

text
(3, 4)
(0, 8)
True
32 96
  1. Baris 4 (np.broadcast_to): menghasilkan view berbentuk (3, 4) terhadap potongan — sebuah jendela ke data lama, bukan salinan.
  2. Baris 6 (diperluas.strides): (0, 8) berarti untuk berpindah satu baris, NumPy “melangkah 0 byte”; semua baris membaca lokasi memori yang sama.
  3. Baris 7 (diperluas.base is potongan): True menegaskan bahwa buffer memori view dan buffer potongan adalah data yang sama.
  4. Baris 8 (nbytes): data fisik hanya 32 byte (empat bilangan bulat), sedangkan atribut nbytes view melaporkan 96 byte seolah-olah data rapat — angka teoretis, bukan memori yang benar-benar dipakai.