Operasi per elemen

Aritmetika +, *, dan ** yang bekerja pada seluruh array sekaligus.

Daftar isi

Pada NumPy, operator aritmetika bekerja pada seluruh array sekaligus tanpa perulangan eksplisit. Topik ini membahas operator +, -, *, /, dan ** untuk array yang sebangun maupun untuk array dengan skalar, serta membandingkan perilaku tersebut dengan operasi list Python. Setelah topik ini dipahami, ekspresi perhitungan dapat ditulis sebagai satu operasi array tanpa perulangan manual.

Arti operasi per elemen#

Operasi array pada NumPy bersifat per elemen (element-wise), yaitu setiap elemen dihitung bersama pasangannya pada posisi yang sama. Dua array yang dijumlahkan harus sebangun, yakni sama bentuknya, dan hasilnya berupa array baru dengan bentuk yang sama.

python
import numpy as np

a = np.array([10, 20, 30, 40])
b = np.array([1, 2, 3, 4])
print(a + b)
print(a - b)
print(a * b)
print(a / b)
text Keluaran
[11 22 33 44]
[ 9 18 27 36]
[ 10  40  90 160]
[10. 10. 10. 10.]

Empat operator di atas diproses dalam satu pola yang sama, yaitu pasangan 10 dengan 1, 20 dengan 2, dan seterusnya. Spasi ekstra pada beberapa baris hanyalah perataan kolom. Perhatikan bahwa hasil pembagian bertipe pecahan meskipun habis dibagi, karena operator / selalu menghasilkan array float64.

Operator pangkat ** mengikuti aturan yang sama, yaitu setiap elemen dipangkatkan dengan pasangannya atau dengan bilangan yang diberikan. Pangkat pecahan berfungsi sebagai akar, sehingga pangkat 0.5 menghasilkan akar kuadrat setiap elemen.

python
import numpy as np

a = np.array([2, 3, 4])
print(a ** 2)
b = np.array([4.0, 9.0, 16.0])
print(b ** 0.5)
text Keluaran
[ 4  9 16]
[2. 3. 4.]

Tipe hasilnya mengikuti operandnya, sehingga pangkat bulat pada array bulat tetap bulat, sedangkan pangkat pecahan menghasilkan pecahan.

Pada tingkat bawah, setiap operator array dipetakan ke fungsi universal (ufunc) milik NumPy, yaitu fungsi yang menerapkan satu operasi pada setiap pasangan elemen. Fungsi seperti np.add dan np.multiply melakukan hal yang sama dengan + dan *, sehingga kedua bentuk dapat saling menggantikan. Bentuk operator tetap menjadi pilihan utama karena lebih mudah dibaca, sedangkan bentuk fungsi berguna ketika operasinya perlu dikirim sebagai argumen ke fungsi lain.

Sebutan fungsi universal menegaskan bahwa satu fungsi yang sama dipakai untuk array satu dimensi, dua dimensi, hingga lebih banyak dimensi. NumPy tidak perlu mengetahui bentuk arraynya terlebih dahulu, karena operasi dilakukan pada pasangan elemen yang posisinya sejajar. Berkat sifat ini, pemahaman tentang operator pada array satu dimensi berlaku tanpa perubahan pada bentuk yang lebih rumit.

Array dua dimensi tetap per elemen#

Aturan per elemen tidak berubah pada array dua dimensi. Elemen yang dihitung tetaplah elemen pada posisi baris dan kolom yang sama, sehingga operasinya terlihat seperti operasi dua array satu dimensi yang dilakukan sekaligus untuk setiap baris.

python
import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[10, 20], [30, 40]])
print(a + b)
print(a * b)
text Keluaran
[[11 22]
 [33 44]]
[[ 10  40]
 [ 90 160]]

Perlu ditegaskan bahwa * pada array NumPy bukan perkalian matriks seperti pada aljabar linear. Perkalian matriks memiliki aturan lain, yaitu baris dikalikan kolom, dan NumPy menyediakan operator @ untuk keperluan itu. Mencampur kedua arti ini adalah sumber kebingungan yang sering, sehingga * sebaiknya selalu dibaca sebagai perkalian per elemen.

Posisi yang dimaksud adalah pasangan baris dan kolom yang sama pada kedua array. Elemen baris pertama kolom pertama dijumlahkan dengan elemen baris pertama kolom pertama, dan seterusnya untuk setiap posisi. Karena itulah kedua array wajib sebangun, yaitu sama jumlah baris dan kolomnya, agar setiap elemen selalu memiliki pasangan.

Operasi array dengan skalar#

Apabila satu operand berupa skalar, nilai skalar itu diterapkan ke setiap elemen array. Tidak ada aturan khusus yang perlu diingat untuk kasus ini, karena hasilnya sama seperti menjumlahkan atau mengalikan array dengan array yang seluruh elemennya bernilai sama.

python
import numpy as np

cetak = np.array([12, 25, 30])
print(cetak + 5)
print(cetak * 2)
print(cetak / 10)
text Keluaran
[17 30 35]
[24 50 60]
[1.2 2.5 3. ]

Array cetak tidak berubah, karena setiap operasi menghasilkan array baru. Interaksi antara array dan skalar ini merupakan kasus paling sederhana dari aturan yang lebih umum bernama broadcasting, yaitu cara NumPy menyelaraskan operand yang bentuknya berbeda.

Penerapan yang lazim adalah konversi satuan. Konversi suhu dari Celcius ke Fahrenheit memakai rumus C × 9 / 5 + 32, dan pada array rumus itu ditulis apa adanya. Urutan operasi tetap mengikuti aturan aritmetika biasa, yaitu * dan / didahulukan sebelum +.

python
import numpy as np

celcius = np.array([20.0, 25.0, 30.0])
fahrenheit = celcius * 9 / 5 + 32
print(fahrenheit)
text Keluaran
[68. 77. 86.]

Keluarannya menampilkan 68, 77, dan 86, persis seperti hasil perhitungan manual satu per satu. Array celcius tidak berubah, karena hasil konversi disimpan pada array baru bernama fahrenheit. Satu ekspresi dengan demikian menggantikan perulangan yang harus ditulis bila datanya masih berupa list.

Kontras dengan list#

Perilaku list Python berbeda dari array NumPy pada operator yang sama. Operator + pada list menyambung dua list menjadi satu, sedangkan * mengulang isi list. Untuk mendapatkan penjumlahan per elemen pada list, perulangan atau pemahaman list harus ditulis sendiri.

python
import numpy as np

list_a = [10, 20, 30]
list_b = [1, 2, 3]
a = np.array(list_a)
b = np.array(list_b)

print(list_a + list_b)
print(a + b)
text Keluaran
[10, 20, 30, 1, 2, 3]
[11 22 33]

Baris pertama memperlihatkan penyambungan, sedangkan baris kedua memperlihatkan penjumlahan per elemen pada array yang dibentuk dari list yang sama. Pengulangan list juga berbeda dari aritmetika, karena [10, 20, 30] * 2 menggandakan isinya, bukan mengalikan nilainya.

python
import numpy as np

print([10, 20, 30] * 2)
text Keluaran
[10, 20, 30, 10, 20, 30]

Makna + yang berbeda pada list dan array menjadi sumber kesalahan yang halus ketika kode bercampur antara keduanya. Kebiasaan yang aman adalah menyelesaikan seluruh perhitungan numerik di wilayah array, lalu mengubah hasilnya kembali menjadi list hanya pada batas akhir program. Dengan demikian, satu konvensi operator dipakai sepanjang perhitungan.

Perbedaan ini bersifat semantik, bukan sekadar gaya penulisan. Pada data besar, selisih waktu eksekusinya juga nyata, tetapi pengukurannya memerlukan teknik tersendiri di luar lingkup topik ini.

Kesalahan umum#

Kesalahan pertama adalah membaca * sebagai perkalian matriks. Pada array NumPy, * selalu mengalikan per elemen, sedangkan perkalian matriks aljabar linear memakai operator @. Kesalahan ini tidak memunculkan error, karena keduanya sah untuk array persegi, tetapi hasilnya berbeda total. Pada array [[1, 2], [3, 4]] misalnya, a * a menghasilkan kuadrat setiap elemen, sedangkan a @ a menghasilkan matriks baru dari penjumlahan perkalian baris dan kolom.

Kesalahan kedua adalah menjumlahkan array dengan jumlah elemen berbeda sambil mengharapkan elemen yang berlebih diabaikan. NumPy menolak operasi tersebut dengan error, karena tidak ada cara penyelarasan yang sah untuk kedua bentuk itu.

python
import numpy as np

a = np.array([1, 2, 3])
try:
    print(a + np.array([1, 2]))
except Exception as e:
    print(f"{type(e).__name__}: {e}")
text Keluaran
ValueError: operands could not be broadcast together with shapes (3,) (2,)

Pesan errornya menyebut broadcasting karena aturan penyelarasan itulah yang menentukan apakah dua bentuk kompatibel. Bentuk (3,) dan (2,) bukan pasangan yang sah, sehingga operasi berhenti dengan ValueError. Perbaikannya adalah menyamakan bentuk kedua array sebelum operasi, atau mengatur ulang data agar pasangan bentuknya memenuhi aturan broadcasting.

Ringkasan#

  • Operator +, -, *, /, dan ** bekerja per elemen, yaitu setiap elemen dihitung bersama pasangan pada posisi yang sama.
  • Operator / selalu menghasilkan array float64 meskipun hasil pembagiannya bilangan bulat.
  • Operasi array dengan skalar menerapkan nilai skalar ke setiap elemen dan merupakan kasus paling sederhana dari broadcasting.
  • * pada array bukan perkalian matriks, sedangkan operator @ adalah operator untuk perkalian matriks.

Referensi#