Setiap kali sesi Python berakhir, seluruh array yang kita hitung ikut hilang dari memori. Program analisis yang nyata hampir selalu bekerja dua tahap: satu skrip menghitung dan menyimpan, skrip lain memuat kembali untuk dianalisis. Agar tahap kedua mendapatkan data yang persis sama, hasil perhitungan perlu disimpan ke berkas dengan format yang mempertahankan semua sifat array — bukan sekadar angkanya.

Format bawaan NumPy untuk keperluan ini adalah berkas .npy: format biner yang menyimpan satu array beserta seluruh metadata pentingnya, yaitu bentuk (shape), tipe data (dtype), dan urutan byte. Kita akan membedah cara menyimpan dengan np.save, apa saja yang tertulis di dalam berkasnya, dan mengapa metadata itu menentukan.

Menyimpan dengan np.save #

np.save() menerima dua argumen utama: nama berkas tujuan dan array yang akan disimpan. Jika nama berkas belum berakhiran .npy, NumPy menambahkan akhiran itu secara otomatis:

python
import numpy as np
import os

suhu = np.array([27.5, 28.1, 26.9, 30.2])
np.save("suhu.npy", suhu)
np.save("data", suhu)   # ekstensi .npy ditambahkan otomatis
berkas = sorted(f for f in os.listdir(".") if f.endswith(".npy"))
print(berkas)

Output yang Dihasilkan:

text
['data.npy', 'suhu.npy']

Mari kita bedah:

  1. Baris 5 (np.save("suhu.npy", suhu)): NumPy menulis isi array suhu ke berkas suhu.npy di direktori kerja saat ini, lengkap dengan metadata bentuk dan dtype-nya.
  2. Baris 6 (np.save("data", suhu)): nama tanpa akhiran tetap diterima; NumPy menyimpannya sebagai data.npy. Kebiasaan menulis akhiran secara eksplisit tetap disarankan agar maksudnya jelas.
  3. Baris 7-8 (os.listdir(...)): daftar berkas disaring hanya untuk akhiran .npy, lalu dicetak; direktori kerja kini memuat dua berkas .npy — bukti bahwa satu pemanggilan np.save menghasilkan satu berkas.

Anatomi Berkas .npy #

Apa sebenarnya yang ditulis NumPy ke dalam berkas ini? Karena formatnya terdokumentasi dan sederhana, kita bisa membacanya langsung sebagai byte mentah. Berkas .npy terdiri dari tiga bagian: magic number pengenal format, panjang header, dan header teks yang menyimpan metadata:

python
import numpy as np

a = np.array([[25.5, 27.0], [26.1, 28.3]])
np.save("suhu.npy", a)
with open("suhu.npy", "rb") as f:
    isi = f.read()
print(isi[:8])
panjang_header = int.from_bytes(isi[8:10], "little")
print(panjang_header)
print(isi[10:10 + panjang_header].decode("ascii").rstrip())

Output yang Dihasilkan:

text
b'\x93NUMPY\x01\x00'
118
{'descr': '<f8', 'fortran_order': False, 'shape': (2, 2), }

Penjelasan tiap bagian:

  1. Baris 6 (print(isi[:8])): delapan byte pertama adalah pengenal format — byte \x93NUMPY menandai berkas NumPy, diikuti \x01\x00 sebagai penanda versi format 1.0.
  2. Baris 7-8: dua byte berikutnya menyimpan panjang header (118) dalam urutan little-endian.
  3. Baris 9: header berupa dict berformat teks. '<f8' berarti float64 little-endian, fortran_order menjelaskan tata letak memori, dan shape: (2, 2) adalah bentuk array. Setelah header, byte berikutnya adalah data binernya yang polos.
Bidang headerArti
descrdtype array, mis. '<f8' untuk float64 little-endian
fortran_orderFalse bila data disimpan baris-per-baris (urutan C)
shapebentuk array dalam tuple

Inilah keunggulan utama format .npy dibanding menyimpan angka dalam teks: pembaca berkas tidak perlu menebak tipe data atau bentuknya, karena semuanya tercantum di header.

Array Terstruktur Juga Tersimpan Utuh #

Metadata pada header tidak terbatas pada array numerik biasa. Array terstruktur (structured array) dengan beberapa kolom bertipe berbeda — sering dipakai untuk data berlabel seperti catatan stok — juga tersimpan lengkap beserta definisi kolomnya:

python
import numpy as np

stok = np.array([("beras", 120, 15000), ("gula", 80, 18000)],
                dtype=[("nama", "U6"), ("stok", "i4"), ("harga", "i4")])
np.save("stok.npy", stok)
b = np.load("stok.npy")
print(b.dtype)
print(b["nama"])
print(b["harga"].sum())

Output yang Dihasilkan:

text
[('nama', '<U6'), ('stok', '<i4'), ('harga', '<i4')]
['beras' 'gula']
33000

Mari kita telaah:

  1. Baris 3-4 (np.array(..., dtype=...)): array terstruktur dua baris dibuat dengan tiga kolom — nama (U6), stok (i4), dan harga (i4).
  2. Baris 5-6: array disimpan lalu dimuat kembali dengan np.load; dtype kompleksnya direkonstruksi persis, termasuk nama dan tipe tiap kolom.
  3. Baris 7-9: kolom dapat diakses seperti biasa; b["harga"].sum() menghitung total harga dari data yang telah melewati siklus simpan-muat tanpa kehilangan apa pun.