Normalisasi per baris dan perhitungan jarak berpasangan adalah dua pola broadcasting yang paling sering muncul dalam pekerjaan data. Keduanya menggantikan perulangan bersarang dengan operasi array yang dieksekusi di kode internal NumPy. Keduanya juga dibangun dari gerakan yang sama, yaitu menyisipkan dimensi 1 pada posisi yang tepat, lalu membiarkan broadcasting menyebarkan nilainya. Topik ini membangun kedua pola tersebut pada data kecil berseed, mulai dari bentuk antara yang terlibat sampai keluaran akhirnya.
Normalisasi per baris#
Standardisasi (standardization) per baris mengurangi setiap elemen dengan rata-rata barisnya, lalu membaginya dengan simpangan baku baris yang sama. Notasi menyatakan rata-rata baris ke-i, sedangkan menyatakan simpangan baku baris yang sama.
Data contohnya adalah pengukuran tiga sensor suhu selama empat jam, yang dibuat dengan default_rng(42) agar keluarannya dapat direproduksi. Rata-rata dan simpangan dihitung per baris dengan axis=1 dan keepdims=True, sehingga hasil agregasinya tetap berbentuk (3, 1) dan siap disiarkan melawan (3, 4). Dengan matriks (m, n) berukuran apa pun, pola yang sama menghasilkan agregasi (m, 1), sehingga tidak ada angka yang perlu diubah saat ukuran data bertambah.
import numpy as np
rng = np.random.default_rng(42)
suhu = np.round(rng.normal(25, 3, size=(3, 4)), 1)
print("data:")
print(suhu)
rata = suhu.mean(axis=1, keepdims=True)
simpangan = suhu.std(axis=1, keepdims=True)
z = (suhu - rata) / simpangan
print("rata-rata per baris:")
print(np.round(rata, 2))
print("z-skor:")
print(np.round(z, 2))data:
[[25.9 21.9 27.3 27.8]
[19.1 21.1 25.4 24.1]
[24.9 22.4 27.6 27.3]]
rata-rata per baris:
[[25.72]
[22.42]
[25.55]]
z-skor:
[[ 0.08 -1.65 0.68 0.9 ]
[-1.34 -0.54 1.2 0.68]
[-0.31 -1.5 0.98 0.83]]Setelah dibagi dengan simpangan baku, setiap baris z memiliki rata-rata 0 dan simpangan 1 sesuai tujuan standardisasi. Nilai 0.08 berarti pengukuran pertama sensor pertama berada sedikit di atas rata-rata barisnya, sedangkan -1.65 berada jauh di bawahnya. Rata-rata per barisnya, yaitu 25.72, 22.42, dan 25.55, sesuai dengan pembuatan data di sekitar angka 25. Simpangan yang digunakan adalah simpangan populasi, yaitu pembagi n secara bawaan NumPy, sehingga nilainya konsisten dengan fungsi std tanpa argumen tambahan. Standardisasi per baris bermanfaat apabila setiap baris mewakili sumber dengan skala berbeda, sehingga perbandingan antarbaris menjadi adil.
Alternatif penulisan tanpa keepdims adalah mengambil hasil agregasi lalu menyisipkan dimensi secara manual, yaitu suhu.mean(axis=1)[:, np.newaxis]. Keduanya menghasilkan bentuk (3, 1) yang sama, sehingga pilihannya hanya soal gaya. Yang tidak dapat diganti adalah bentuk agregasinya, karena (3,) tidak akan pernah cocok dengan dimensi terakhir (3, 4).
Jarak berpasangan antar titik#
Jarak antar setiap pasangan titik dihitung dengan rumus jarak Euclidean (Euclidean distance), yaitu akar dari jumlah kuadrat selisih koordinatnya.
Empat titik pada bidang disimpan sebagai array (4, 2). Selisih antar titik dihitung dengan broadcasting tiga dimensi sekaligus, yaitu (4, 1, 2) dikurangi (1, 4, 2), sehingga menghasilkan array selisih berbentuk (4, 4, 2). Kuadrat selisihnya dijumlahkan pada sumbu terakhir, lalu diakarkan. Reduksinya menggunakan axis=-1, yaitu sumbu terakhir yang memuat pasangan koordinat, sehingga sumbu baris dan kolom matriks jarak tetap utuh. Hasil akhirnya adalah matriks jarak berbentuk (4, 4), yaitu satu sel untuk setiap pasangan titik.
import numpy as np
rng = np.random.default_rng(42)
titik = np.round(rng.uniform(0, 10, size=(4, 2)), 1)
print("titik:")
print(titik)
selisih = titik[:, np.newaxis, :] - titik[np.newaxis, :, :]
jarak = np.sqrt((selisih ** 2).sum(axis=-1))
print("matriks jarak:")
print(np.round(jarak, 2))titik:
[[7.7 4.4]
[8.6 7. ]
[0.9 9.8]
[7.6 7.9]]
matriks jarak:
[[0. 2.75 8.68 3.5 ]
[2.75 0. 8.19 1.35]
[8.68 8.19 0. 6.96]
[3.5 1.35 6.96 0. ]]Matriks jarak bersifat simetris dan diagonalnya nol, karena jarak satu titik dengan dirinya sendiri selalu nol. Nilai 2.75 pada posisi [0, 1] adalah jarak antara titik [7.7, 4.4] dan titik [8.6, 7.0]. Satu sel matriks dapat diverifikasi dengan menghitung ulang pasangannya secara manual.
import numpy as np
p = np.array([7.7, 4.4])
q = np.array([8.6, 7.0])
d = np.sqrt(((p - q) ** 2).sum())
print(np.round(d, 2))2.75Hasilnya sama dengan nilai [0, 1] pada matriks jarak, sehingga pola tiga dimensinya dapat dipercaya. Verifikasi lain yang murah adalah memastikan simetrinya, misalnya dengan membandingkan matriks dengan transposnya. Verifikasi semacam ini layak dilakukan setiap kali pola broadcasting bertingkat disusun pertama kali. Perlu diperhatikan pula ukuran antaranya, karena array selisih tumbuh sebanding dengan kuadrat banyaknya titik, yaitu m * m * d elemen. Pada empat titik, array selisih hanya memuat 32 angka, sedangkan pada seribu titik array yang sama memuat dua juta angka.
Membandingkan dengan perulangan eksplisit#
Broadcasting sering lebih mudah dipahami apabila dibandingkan dengan versi perulangannya. Perbandingan ini juga menegaskan bahwa broadcasting bukan sekadar trik penulisan, melainkan cara menata perhitungan yang setara secara matematis. Mekanisme jarak berpasangan dapat dihitung ulang dengan dua perulangan bersarang pada tiga titik yang jaraknya mudah dihitung manual, yaitu (0, 0), (3, 4), dan (6, 8).
import numpy as np
titik = np.array([[0.0, 0.0], [3.0, 4.0], [6.0, 8.0]])
jarak = np.zeros((3, 3))
for i in range(3):
for j in range(3):
jarak[i, j] = np.sqrt(((titik[i] - titik[j]) ** 2).sum())
print(np.round(jarak, 2))[[ 0. 5. 10.]
[ 5. 0. 5.]
[10. 5. 0.]]Versi perulangan mengalokasikan array kosong lebih dahulu, lalu mengisi setiap sel satu per satu. Versi broadcasting menyusun seluruh selisih sekaligus dan menjalankan reduksinya di kode internal NumPy. Kesetaraan kedua versi dapat dipastikan dengan np.allclose apabila keduanya dihitung pada data yang sama. Untuk tiga titik, perbedaannya tidak terasa, sedangkan untuk ribuan titik versi broadcasting jauh lebih ringkas dan lebih cepat. Jarak 5 antara (0, 0) dan (3, 4) dapat diverifikasi dengan teorema Pythagoras, sehingga kedua versi saling menguatkan. Perulangan dua tingkat seperti ini adalah bentuk yang paling sering digantikan broadcasting dalam pekerjaan data sehari-hari.
Ringkasan#
- Standardisasi per baris memerlukan
keepdims=Trueagar hasil agregasinya berbentuk(n, 1)dan dapat disiarkan melawan array aslinya. - Jarak berpasangan dibangun dari selisih
(m, 1, d) - (1, m, d)yang disiarkan menjadi(m, m, d), lalu direduksi pada sumbu terakhir. - Kedua pola menggantikan perulangan bersarang dengan broadcasting, dan setiap bentuk antaranya dapat diperiksa dengan
shape.
Referensi#
- Broadcasting — NumPy — Dokumentasi resmi aturan broadcasting yang menjadi dasar kedua pola di atas.
- Universal functions (ufunc) — NumPy — Bagian broadcasting pada dokumentasi ufunc untuk operasi per elemen yang digunakan kedua pola.