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:
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:
[[11500 14000 9750 8000]
[12500 13500 10750 9000]
[12000 14500 10250 8500]]
(3, 4)Mari kita bedah apa yang dilakukan interpreter pada tiap baris:
- Baris 3-5 (
harga):np.arraymembentuk matriks 3 × 4 — baris untuk cabang, kolom untuk produk, satuan rupiah. - Baris 6 (
potongan): array 1-D berbentuk(4,); tiap angka adalah potongan untuk satu produk dan berlaku di semua cabang. - 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). - 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):
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:
[[11000 14000 9000 7000]
[12250 13750 10250 8250]
[11250 14250 9250 7250]]
(3, 4)- Baris 6 (
potongan_cabang): nested list menghasilkan array berbentuk(3, 1)— tiga baris, satu kolom. - Baris 8 (
harga - potongan_cabang): kolom tunggal diperluas menyamping menjadi empat kolom; tiap baris matriks dikurangi potongan cabangnya sendiri. - Baris 9-10: hasil tetap
(3, 4); arah perluasan berbeda, bentuk hasil sama.
Tabel berikut merangkum peran tiap array pada kedua skenario:
| Array | Bentuk | Peran 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:
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:
(3, 4)
(0, 8)
True
32 96- Baris 4 (
np.broadcast_to): menghasilkan view berbentuk(3, 4)terhadappotongan— sebuah jendela ke data lama, bukan salinan. - Baris 6 (
diperluas.strides):(0, 8)berarti untuk berpindah satu baris, NumPy “melangkah 0 byte”; semua baris membaca lokasi memori yang sama. - Baris 7 (
diperluas.base is potongan):Truemenegaskan bahwa buffer memori view dan bufferpotonganadalah data yang sama. - Baris 8 (
nbytes): data fisik hanya 32 byte (empat bilangan bulat), sedangkan atributnbytesview melaporkan 96 byte seolah-olah data rapat — angka teoretis, bukan memori yang benar-benar dipakai.