Structured array pada Chapter Structured Array memakai dtype yang kita definisikan langsung di dalam pemanggilan np.array. Ketika skema data menjadi lebih kompleks — field bertumpuk, lebar byte tertentu, atau dipakai bersama beberapa fungsi — skema tersebut layak dirancang sebagai custom dtype yang berdiri sendiri, lalu dipakai ulang di banyak tempat.

Bab ini merangkum perangkat pembuatannya: dua cara menuliskan dtype, field bersarang, pembacaan array dari buffer byte mentah, serta pembuatan array dari fungsi dan perulangan terindeks. Semuanya berkisar pada satu gagasan: array adalah penampung byte, dan dtype adalah cara kita menafsirkan byte itu.

Menyusun dtype: Bentuk Tuple dan Dict #

Cara paling lazim adalah list of tuples seperti sebelumnya. NumPy juga menerima bentuk dict yang menulis nama, format, dan opsional offset byte secara eksplisit — keduanya menghasilkan dtype yang identik:

python
import numpy as np

dt_tuple = np.dtype([("kode", "U4"), ("stok", "i4"), ("harga", "f8")])
dt_dict = np.dtype({"names": ["kode", "stok", "harga"],
                    "formats": ["U4", "i4", "f8"]})
print(dt_tuple == dt_dict)
print(dt_tuple.fields)
print(dt_tuple.itemsize)

Output yang Dihasilkan:

text
True
{'kode': (dtype('<U4'), 0), 'stok': (dtype('int32'), 16), 'harga': (dtype('float64'), 20)}
28

Mari kita bedah:

  1. Baris 3-5: dua penulisan menghasilkan dtype yang sama — perbandingan == mengembalikan True. Bentuk tuple ringkas; bentuk dict lebih panjang tetapi bisa mengatur offset dan itemsize manual.
  2. Baris 7 (dt_tuple.fields): dict ini memetakan nama field ke pasangan (tipe, offset_byte). Field kode menempati offset 0, stok offset 16, dan harga offset 20.
  3. Baris 8 (itemsize): 4 karakter x 4 byte + 4 + 8 = 28 byte per record, disusun rapat tanpa padding.

Field Bersarang #

Nilai sebuah field boleh berupa dtype lain yang juga memiliki field — struktur menyerupai record di dalam record:

python
import numpy as np

dt = np.dtype([("produk", "U8"), ("dimensi", [("panjang", "f4"), ("lebar", "f4")])])
kardus = np.array([("Buku", (21.0, 14.5)), ("Laptop", (33.5, 24.0))], dtype=dt)
print(kardus)
print(kardus["dimensi"]["panjang"])
print(kardus["dimensi"].shape)

Output yang Dihasilkan:

text
[('Buku', (21. , 14.5)) ('Laptop', (33.5, 24. ))]
[21.  33.5]
(2,)

Penjelasan singkat:

  1. Baris 3: field dimensi bertipe dtype bersarang dengan dua subfield f4; dalam satu record, nilai ini menempati dua float 32-bit berurutan.
  2. Baris 5 (print(kardus)): nilai bersarang dicetak sebagai tuple di dalam tuple record.
  3. Baris 6-7: akses berantai kardus["dimensi"]["panjang"] mengekstrak satu subfield dari seluruh record, menghasilkan array float32 satu dimensi.

Membaca Buffer Byte dengan frombuffer #

Custom dtype paling terasa gunanya saat menerima data biner dari luar — hasil baca file, soket jaringan, atau perpustakaan C. Fungsi np.frombuffer menafsirkan buffer byte sesuai dtype tanpa menyalin data. Untuk keperluan uji, buffer “dari luar” itu kita tiru dengan tobytes():

python
import numpy as np

dt = np.dtype([("id", "i4"), ("suhu", "f4")])
sumber = np.array([(1, 27.5), (2, 28.0), (3, 29.5)], dtype=dt)
buf = sumber.tobytes()
print(len(buf))
hasil = np.frombuffer(buf, dtype=dt)
print(hasil)
print(hasil["suhu"].mean())

Output yang Dihasilkan:

text
24
[(1, 27.5) (2, 28. ) (3, 29.5)]
28.333334

Bedah baris demi baris:

  1. Baris 4 (sumber.tobytes()): tiga record dikonversi menjadi 24 byte mentah — 3 x (4 + 4) — persis seperti buffer yang akan dikirim perangkat atau ditulis file biner.
  2. Baris 6 (np.frombuffer(buf, dtype=dt)): interpreter membagi buffer per 8 byte dan menafsirkan 4 byte pertama sebagai i4 serta 4 byte berikutnya sebagai f4, tanpa menyalin memori.
  3. Baris 8: hasilnya array structured biasa, sehingga agregasi per field langsung berfungsi; mean() menghasilkan 28.333334 karena suhu bertipe f4.

fromfunction dan ndenumerate #

Dua perangkat terakhir melengkapi pembuatan array. np.fromfunction mengisi array dengan memanggil fungsi atas koordinat indeks, sedangkan np.ndenumerate mengiterasi pasangan (indeks, nilai) pada array apa pun:

python
import numpy as np

tabel = np.fromfunction(lambda i, j: (i + 1) * (j + 1), (2, 3), dtype=int)
print(tabel)
for idx, nilai in np.ndenumerate(tabel):
    print(idx, nilai)

Output yang Dihasilkan:

text
[[1 2 3]
 [2 4 6]]
(0, 0) 1
(0, 1) 2
(0, 2) 3
(1, 0) 2
(1, 1) 4
(1, 2) 6

Penjelasan:

  1. Baris 3 (np.fromfunction(...)): fungsi dipanggil untuk setiap pasangan koordinat (i, j) — hasilnya tabel perkalian kecil 1..2 x 1..3. Argumen dtype=int mengatur tipe koordinat yang diteruskan ke fungsi.
  2. Baris 6-7 (np.ndenumerate): setiap langkah menghasilkan tuple indeks seperti (1, 2) beserta nilai elemennya, terurut sesuai layout memori.
  3. Pola for idx, nilai in np.ndenumerate(a) menggantikan kombinasi dua loop range yang rentan salah indeks — versi lanjutannya dengan np.nditer dibahas pada Chapter Array Iteration.

Dengan perangkat di atas, array tidak lagi terbatas pada bentuk “angka berderet”: skema datanya bisa dirancang mengikuti domain masalah, sekaligus tetap bisa dibaca dari dan ke buffer byte mentah.