File I/O: save, load, npz and memory-mapped arrays
Persist arrays without losing dtype or shape, read text files robustly, and work with arrays that do not fit in memory.
npy and npz
NumPy's own formats store dtype, shape and order alongside the bytes, so a round trip is exact. CSV cannot do that: it loses dtype and floats come back as an approximation.
a = np.arange(6).reshape(2, 3)
np.save("a.npy", a) # single array
np.load("a.npy")
np.savez("bundle.npz", x=a, y=a * 2) # several arrays, uncompressed
np.savez_compressed("bundle.npz", x=a, y=a * 2) # smaller, slower to write
data = np.load("bundle.npz")
sorted(data.keys()) # ['x', 'y']
data["x"]
# the easy route to a huge file: do not read it all
mm = np.load("a.npy", mmap_mode="r")
mm[0]| Format | Keeps dtype | Size | Use for |
|---|---|---|---|
| .npy | Yes | Raw bytes | One array, exact round trip |
| .npz | Yes | Raw bytes | Several named arrays in one file |
| .npz compressed | Yes | Smaller | Archiving, slow random access |
| CSV/text | No | Large | Interchange with other tools |
| memmap | Yes | On disk | Arrays larger than RAM |
Text files
np.savetxt("data.csv", a, delimiter=",", fmt="%.3f",
header="a,b,c", comments="")
np.loadtxt("data.csv", delimiter=",", skiprows=1) # numeric, fast, all-or-nothing
# tolerant version: handles missing values, headers and mixed types
t = np.genfromtxt("data.csv", delimiter=",", names=True,
dtype=None, encoding="utf-8")
t["a"]
d = np.loadtxt("data.csv", delimiter=",", skiprows=1,
usecols=(0, 2), max_rows=100)loadtxtfails on a single bad token;genfromtxtfills it withnaninstead.- Use
usecolsandmax_rowsto avoid reading columns you do not need. - Text I/O is a convenience, not a storage strategy: it is slower and roughly twice the size of binary.
Arrays bigger than memory
# create a file-backed array, then write through it in blocks
mm = np.memmap("big.dat", dtype=np.float32, mode="w+", shape=(10000, 10000))
for start in range(0, 10000, 500):
mm[start:start + 500] = 0.0
mm.flush()
# later, and on a machine without enough RAM to hold it all
mm = np.memmap("big.dat", dtype=np.float32, mode="r", shape=(10000, 10000))
mm[42, :10] # only the pages touched are read from disk
mm[:, 3].mean() # a column still walks the whole file: slow but possible⚠️
Fancy indexing a memmap materialises the result in RAM.
mm[rows] with thousands of indices can allocate more memory than reading the file sequentially would — slice, do not gather.FAQ
Why did my CSV numbers come back slightly different?
Text parsers read decimals as doubles and CSV cannot record the original dtype. For an exact round trip use
.npy or .npz.Is a memmap faster than loading?
Not necessarily. Random access to disk is slower than reading once into RAM. Memmap wins when the file is larger than memory or when you only touch a small part of it.
Related
Views, copies and memory layout Structured arrays, datetimes and string dtypes
Last refreshed 2026-09-18.