Operasi antara array dan skalar adalah bentuk broadcasting yang paling sederhana dan paling sering digunakan. Pola seperti a + 10 sudah digunakan sejak modul dasar array, tetapi mekanisme yang membuat satu bilangan dapat dijumlahkan ke seluruh elemen belum dijelaskan. Topik ini membahas cara NumPy memperlakukan skalar pada operasi aritmetika, yaitu sebagai operand berdimensi nol, beserta alasan pola ini hemat memori.
Operasi aritmetika dengan skalar#
Menambahkan, mengalikan, atau memangkatkan array dengan satu bilangan diterapkan ke setiap elemen secara seragam. Seluruh operator aritmetika Python, yaitu +, -, *, /, //, %, dan **, berlaku antara array dan skalar dengan mekanisme yang sama. Tidak ada perulangan yang ditulis secara eksplisit, dan bentuk array tidak berubah.
import numpy as np
a = np.arange(5)
print("a :", a)
print("a + 10 :", a + 10)
print("a * 2 :", a * 2)
print("a ** 2 :", a ** 2)a : [0 1 2 3 4]
a + 10 : [10 11 12 13 14]
a * 2 : [0 2 4 6 8]
a ** 2 : [ 0 1 4 9 16]Setiap elemen a mengalami operasi yang sama dengan bilangan 10 atau 2, dan keluarannya selalu array baru dengan bentuk yang sama. Skalar yang dilibatkan juga tidak harus bilangan bulat. Ketika array bilangan bulat bertemu skalar pecahan, tipe hasilnya naik ke tipe yang lebih umum agar bagian desimalnya tetap tersimpan.
import numpy as np
a = np.arange(5)
print("a + 10.5 :", a + 10.5)
print("a / 2 :", a / 2)a + 10.5 : [10.5 11.5 12.5 13.5 14.5]
a / 2 : [0. 0.5 1. 1.5 2. ]Penjumlahan dengan 10.5 menghasilkan array float karena tipe asal a tidak dapat menampung bagian desimalnya. Pada array berdimensi dua, perilaku ini identik karena tidak ada dimensi yang perlu dicocokkan secara khusus.
Operator perbandingan mengikuti pola yang sama, yaitu skalar diterapkan ke setiap elemen dan menghasilkan array boolean.
import numpy as np
a = np.arange(5)
print("a > 2 :", a > 2)
print("a == 3 :", a == 3)a > 2 : [False False False True True]
a == 3 : [False False False True False]Setiap sel boolean menjawab pertanyaan perbandingan untuk elemen pada posisi yang sama. Array boolean seperti ini menjadi bahan utama penyaringan data, dan pola skalar pada perbandingannya persis sama dengan pola pada aritmetika.
import numpy as np
m = np.arange(6).reshape(2, 3)
print(m / 2)[[0. 0.5 1. ]
[1.5 2. 2.5]]Pembagian m / 2 menghasilkan array bertipe pecahan karena pembagian sejati (true division) selalu menghasilkan float, meskipun m berisi bilangan bulat. Skalar 2 tetap diterapkan pada keenam elemen tanpa terkecuali.
Skalar sebagai operand berdimensi nol#
Skalar dalam NumPy diperlakukan sebagai array berdimensi nol dengan bentuk (). Objek np.array(10) memperlihatkan bentuk ini secara eksplisit.
import numpy as np
skalar = np.array(10)
print("bentuk :", skalar.shape)
print("ndim :", skalar.ndim)
print("10 + a :", 10 + np.arange(5))bentuk : ()
ndim : 0
10 + a : [10 11 12 13 14]Objek np.array(10) adalah array sungguhan yang bentuknya () dan berdimensi 0, sehingga setara dengan skalar Python pada operasi apa pun. Aturan penyelarasan melapisi bentuk () dengan dimensi bernilai 1 sebanyak yang diperlukan, sehingga bentuk nol dimensi kompatibel dengan bentuk apa pun. Fungsi np.broadcast_shapes memperlihatkan penyelarasan ini tanpa perlu membuat array sama sekali.
import numpy as np
print(np.broadcast_shapes((), (3, 4)))
print(np.broadcast_shapes((), (5,)))(3, 4)
(5,)Bentuk () dilapisi menjadi (1, 1) ketika bertemu (3, 4), lalu kedua dimensi 1 diperluas menjadi 3 dan 4. Mekanismenya sama dengan penyelarasan pada topik aturan broadcasting, hanya kali ini tanpa dimensi yang ditulis secara eksplisit pada operandnya.
Perlu dibedakan dari array satu elemen dengan bentuk (1,). Bentuk (1,) adalah array satu dimensi yang kebetulan panjangnya satu, sedangkan () adalah array tanpa sumbu sama sekali. Keduanya tetap dapat disiarkan karena nilai 1 pada setiap dimensi selalu dapat diperluas.
import numpy as np
print(np.broadcast_shapes((1,), (3, 4)))(3, 4)Bentuk (1,) dilapisi menjadi (1, 1) terlebih dahulu, lalu keduanya diperluas seperti skalar. Perbedaan () dan (1,) jarang terasa pada operasi tunggal, tetapi keduanya penting dibedakan saat bentuk antara diperiksa untuk melacak masalah.
Skalar tidak dijadikan array penuh#
NumPy tidak membangun array berisi nilai yang sama sebanyak elemen a sebelum menjumlahkan. Skalar Python tidak diubah menjadi array terlebih dahulu, melainkan dibaca ulang pada setiap elemen oleh kode internal ufunc, serupa dengan perilaku stride 0 yang digunakan np.broadcast_to. Perilaku inilah yang membuat a + 10 tetap efisien pada array besar, karena memori tambahan hanya digunakan untuk array keluaran.
Hasil yang sama dapat dibuat secara eksplisit dengan np.full, tetapi cara itu mengalokasikan array operand baru.
import numpy as np
a = np.arange(5)
penuh = np.full(5, 10)
print("a :", a)
print("penuh :", penuh)
print("a + penuh:", a + penuh)a : [0 1 2 3 4]
penuh : [10 10 10 10 10]
a + penuh: [10 11 12 13 14]Keluarannya identik dengan a + 10, tetapi penuh menempati memori untuk lima elemen, sedangkan skalar hanya memerlukan satu nilai. Selisihnya terasa pada array besar, karena array penuh tumbuh sebanding dengan banyaknya elemen, sedangkan skalar tetap satu bilangan. Perbedaan ini murni perkara memori dan keterbacaan, karena hasil perhitungannya tidak pernah berbeda.
Kesalahan umum#
Kesalahan yang sering terjadi adalah menggabungkan operator penugasan in-place (in-place) dengan skalar pecahan pada array bilangan bulat. Operator += dianggap menulis ulang array asal, sehingga tipe datanya tidak boleh berubah.
import numpy as np
a = np.arange(5)
try:
a += 0.5
except Exception as e:
print(f"{type(e).__name__}: {e}")UFuncTypeError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'Pesan tersebut menyatakan bahwa keluaran add bertipe float64 tidak dapat dituangkan kembali ke int64. Perbaikannya adalah menggunakan penjumlahan biasa, yaitu a = a + 0.5, yang membuat array baru bertipe float tanpa menimpa array asalnya. Alternatif lain adalah mengonversi array terlebih dahulu dengan a = a.astype(float) sebelum operator += digunakan.
Kesalahan kedua yang lazim adalah mengira operasi dengan skalar mengubah array asalnya. Operasi aritmetika selalu menghasilkan array baru, sedangkan array asal tetap utuh.
import numpy as np
a = np.arange(5)
b = a + 10
print("a tetap :", a)
print("b baru :", b)a tetap : [0 1 2 3 4]
b baru : [10 11 12 13 14]Variabel b menampung hasil penjumlahan, sedangkan a tidak tersentuh. Apabila array asal memang ingin diperbarui, gunakan a += 10 dengan pertimbangan tipe data seperti pada kesalahan pertama di atas.
Ringkasan#
- Skalar diperlakukan sebagai array berdimensi nol dengan bentuk
(), sehingga kompatibel dengan bentuk array apa pun. - Operasi dengan skalar diterapkan ke setiap elemen tanpa mengubah bentuk array.
- Skalar tidak dijadikan array penuh, sehingga memori tambahan hanya digunakan untuk array keluaran.
Referensi#
- Broadcasting — NumPy — Dokumentasi resmi aturan broadcasting, termasuk kasus operand berdimensi nol.
- NumPy: the absolute basics for beginners — NumPy — Panduan resmi dasar-dasar NumPy, termasuk aritmetika array dengan bilangan tunggal.