Aturan pertama NumPy tetap berlaku: lebih baik menghitung ter-vektorisasi daripada berulang elemen demi elemen, sebagaimana dibahas pada Chapter Loop Python vs Vectorization. Namun ada situasi yang memang menuntut perulangan — streaming data ke fungsi eksternal, operasi yang tidak bisa diekspresikan sebagai ufunc, atau algoritme yang keadaannya bergantung pada elemen sebelumnya. Untuk kasus itu, np.nditer adalah iterator bawaan NumPy yang bekerja di level C: urutan kunjungannya mengikuti layout memori sehingga jauh lebih ramah cache dibanding loop for bersarang Python.
Bab ini menelusuri np.nditer dari bentuk dasar, penulisan elemen, konversi dtype saat iterasi, urutan kunjungan, hingga pembantu kecil np.ndenumerate.
Dasar nditer dan external_loop #
Secara bawaan np.nditer mengunjungi setiap elemen dan menghasilkannya sebagai array 0-dimensi. Flag external_loop mengubah perilaku itu: iterator menyerahkan blok sekaligus dalam bentuk array 1-dimensi:
import numpy as np
matriks = np.array([[1, 2, 3], [4, 5, 6]])
for x in np.nditer(matriks):
print(x, end=" ")
print()
it = np.nditer(matriks, flags=["external_loop"])
for x in it:
print(x, type(x))Output yang Dihasilkan:
1 2 3 4 5 6
[1 2 3 4 5 6] <class 'numpy.ndarray'>Mari kita bedah:
- Baris 4-6: iterasi dasar menghasilkan enam objek array 0-dimensi — setiap
xbukan bilangan Python, tetapi array berukuran(). Itulah sebabnya pencetakan memakaiend=" "agar enam hasil menyatu satu baris. - Baris 8-10 (
flags=["external_loop"]): seluruh elemen diserahkan dalam satu array 1-dimensi[1 2 3 4 5 6]. Jumlah kunjungan Python turun dari enam menjadi satu — inilah sumber efisiensi flag tersebut. - Urutan kunjungan mengikuti urutan memori, yang untuk array baru adalah baris demi baris.
Menulis ke Array dengan op_flags #
Iterator bawaan bersifat read-only: menugaskan x = ... hanya mengganti variabel lokal. Agar elemen benar-benar berubah, minta izin tulis lewat op_flags=["readwrite"] dan tugaskan lewat x[...]:
import numpy as np
a = np.array([1.0, 2.0, 3.0])
for x in np.nditer(a, op_flags=["readwrite"]):
x[...] = x * 2
print(a)Output yang Dihasilkan:
[2. 4. 6.]Penjelasan singkat:
- Baris 4 (
op_flags=["readwrite"]): iterator diizinkan membaca sekaligus menulis buffer array. - Baris 5 (
x[...] = x * 2): sintaksx[...]berarti “seluruh isi elemen yang sedang dikunjungi”; penugasan menulis langsung ke memori array asli. - Baris 6: array
aberubah permanen menjadi[2. 4. 6.]tanpa salinan tambahan.
Buffered Iterator untuk Konversi dtype #
Flag buffered memungkinkan iterator menyediakan elemen dalam dtype yang berbeda dari array aslinya melalui op_dtypes — data dikonversi per blok di buffer sementara:
import numpy as np
b = np.array([1, 2, 3], dtype=np.int32)
for x in np.nditer(b, flags=["buffered"], op_dtypes=[np.float64]):
print(x, x.dtype)Output yang Dihasilkan:
1.0 float64
2.0 float64
3.0 float64Bedah:
- Baris 4: tanpa
buffered,op_dtypesakan ditolak karena iterator tidak boleh menyentuh buffer asli. - Baris 5: setiap elemen
int32dikonversi kefloat64saat diserahkan; arraybsendiri tetapint32— konversi terjadi di buffer, bukan di array. - Pola ini berguna ketika fungsi penerima menuntut dtype tertentu sementara data disimpan hemat memori.
Urutan Iterasi Mengikuti Memory Layout #
Bawaan order='K' membuat iterator mengunjungi elemen dalam urutan sedekat mungkin dengan urutan memori. Array yang disalin ke layout kolom (Fortran-order) pun akan dikunjungi kolom demi kolom:
import numpy as np
c = np.arange(6).reshape(2, 3)
f = np.asfortranarray(c)
print([int(x) for x in np.nditer(c)])
print([int(x) for x in np.nditer(f)])
print([int(x) for x in np.nditer(f, order="C")])Output yang Dihasilkan:
[0, 1, 2, 3, 4, 5]
[0, 3, 1, 4, 2, 5]
[0, 1, 2, 3, 4, 5]Penjelasan:
- Baris 5: array
cberlayout baris (C order), sehingga kunjungan berurutan0..5. - Baris 6:
fmenampung data yang sama tetapi berlayout kolom; iterator bawaan mengikutinya —0, 3, 1, 4, 2, 5— agar akses memori tetap berurutan. Konsep layout dibahas mendalam pada Chapter Contiguous vs Non-contiguous. - Baris 7 (
order="C"): argumen ini memaksa urutan logis baris-per-baris berapa pun layout memorinya, dengan konsekuensi lompatan memori pada arrayf.
ndenumerate: Indeks Bersama Nilai #
Ketika perulangan memang perlu tahu posisi elemen, np.ndenumerate menyandingkan tuple indeks dengan nilainya tanpa dua loop bersarang:
import numpy as np
suhu = np.array([[27.5, 26.9], [28.3, 25.8]])
for idx, nilai in np.ndenumerate(suhu):
if nilai < 27.0:
print(idx, nilai)Output yang Dihasilkan:
(0, 1) 26.9
(1, 1) 25.8Hasilnya menunjukkan dua posisi bersuhu di bawah 27 — (baris, kolom) — beserta nilainya. Sintaksnya identik dengan enumerate pada list Python, tetapi bekerja pada array berdimensi berapa pun.
Ringkasan perangkat iterasi:
| Perangkat | Menghasilkan | Cocok untuk |
|---|---|---|
for x in np.nditer(a) | elemen sebagai array 0-d | kunjungan berurutan hemat memori |
flags=["external_loop"] | blok array 1-d | mengurangi jumlah kunjungan Python |
op_flags=["readwrite"] | elemen yang boleh ditulis | transformasi in place |
flags=["buffered"] + op_dtypes | elemen berdtype lain | konversi saat iterasi |
np.ndenumerate(a) | pasangan (indeks, nilai) | operasi yang butuh posisi |