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:

python
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:

text
1 2 3 4 5 6 
[1 2 3 4 5 6] <class 'numpy.ndarray'>

Mari kita bedah:

  1. Baris 4-6: iterasi dasar menghasilkan enam objek array 0-dimensi — setiap x bukan bilangan Python, tetapi array berukuran (). Itulah sebabnya pencetakan memakai end=" " agar enam hasil menyatu satu baris.
  2. 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.
  3. Urutan kunjungan mengikuti urutan memori, yang untuk array baru adalah baris demi baris.

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[...]:

python
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:

text
[2. 4. 6.]

Penjelasan singkat:

  1. Baris 4 (op_flags=["readwrite"]): iterator diizinkan membaca sekaligus menulis buffer array.
  2. Baris 5 (x[...] = x * 2): sintaks x[...] berarti “seluruh isi elemen yang sedang dikunjungi”; penugasan menulis langsung ke memori array asli.
  3. Baris 6: array a berubah 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:

python
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:

text
1.0 float64
2.0 float64
3.0 float64

Bedah:

  1. Baris 4: tanpa buffered, op_dtypes akan ditolak karena iterator tidak boleh menyentuh buffer asli.
  2. Baris 5: setiap elemen int32 dikonversi ke float64 saat diserahkan; array b sendiri tetap int32 — konversi terjadi di buffer, bukan di array.
  3. 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:

python
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:

text
[0, 1, 2, 3, 4, 5]
[0, 3, 1, 4, 2, 5]
[0, 1, 2, 3, 4, 5]

Penjelasan:

  1. Baris 5: array c berlayout baris (C order), sehingga kunjungan berurutan 0..5.
  2. Baris 6: f menampung 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.
  3. Baris 7 (order="C"): argumen ini memaksa urutan logis baris-per-baris berapa pun layout memorinya, dengan konsekuensi lompatan memori pada array f.

ndenumerate: Indeks Bersama Nilai #

Ketika perulangan memang perlu tahu posisi elemen, np.ndenumerate menyandingkan tuple indeks dengan nilainya tanpa dua loop bersarang:

python
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:

text
(0, 1) 26.9
(1, 1) 25.8

Hasilnya 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:

PerangkatMenghasilkanCocok untuk
for x in np.nditer(a)elemen sebagai array 0-dkunjungan berurutan hemat memori
flags=["external_loop"]blok array 1-dmengurangi jumlah kunjungan Python
op_flags=["readwrite"]elemen yang boleh ditulistransformasi in place
flags=["buffered"] + op_dtypeselemen berdtype lainkonversi saat iterasi
np.ndenumerate(a)pasangan (indeks, nilai)operasi yang butuh posisi