NumPy 文件输入输出

在实际的数据处理和科学计算中,经常需要将数组保存到磁盘以便后续使用,或从外部文件加载数据。NumPy提供了多种文件输入输出函数,支持二进制格式和常见文本格式,能够高效地读写数组数据。

1. 二进制格式

NumPy自定义的二进制格式(.npy.npz)能够完整保存数组的形状、数据类型和数据,且读写速度非常快,是保存NumPy数组的首选方式。

1.1 保存单个数组:np.savenp.load

np.save(file, arr) 将单个数组保存为 .npy 文件。np.load(file).npy.npz 文件加载数组。

import numpy as np

arr = np.array([1, 2, 3, 4, 5])

# 保存到文件(默认扩展名 .npy)
np.save('my_array.npy', arr)

# 加载数组
loaded_arr = np.load('my_array.npy')
print("加载的数组:", loaded_arr)

输出:

加载的数组: [1 2 3 4 5]

1.2 保存多个数组:np.saveznp.savez_compressed

np.savez 将多个数组保存到单个 .npz 文件(未压缩),可以通过关键字参数为数组命名。加载后得到一个类似字典的对象,通过数组名访问。np.savez_compressed 类似,但使用压缩减少文件大小。

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 保存多个数组(未压缩)
np.savez('arrays.npz', array1=a, array2=b)

# 压缩保存
np.savez_compressed('arrays_compressed.npz', array1=a, array2=b)

# 加载 .npz 文件
data = np.load('arrays.npz')
print("文件中的数组名:", list(data.keys()))
print("array1:", data['array1'])
print("array2:", data['array2'])

输出:

文件中的数组名: ['array1', 'array2']
array1: [1 2 3]
array2: [4 5 6]
注意: np.load 加载 .npz 文件返回的对象在使用完后最好关闭(或使用上下文管理器),以释放文件句柄。例如:with np.load('arrays.npz') as data: ...

2. 文本格式

文本格式(如 CSV、TXT)便于人类阅读和与其他程序交换数据。NumPy 提供了 savetxtloadtxt 以及更强大的 genfromtxt 函数。

2.1 np.savetxtnp.loadtxt

np.savetxt(fname, arr, delimiter=' ', ...) 将数组保存为文本文件。np.loadtxt(fname, delimiter=None, ...) 从文本文件加载数据,要求数据规则(每行列数相同)。

arr = np.array([[1, 2, 3], [4, 5, 6]])

# 保存为 CSV 文件(逗号分隔)
np.savetxt('data.csv', arr, delimiter=',')

# 从 CSV 加载
loaded = np.loadtxt('data.csv', delimiter=',')
print("从 CSV 加载:\n", loaded)

# 可以指定跳过表头等
# 例如保存时添加表头
np.savetxt('data_with_header.csv', arr, delimiter=',', header='col1,col2,col3', comments='')
# 加载时跳过第一行
loaded_header = np.loadtxt('data_with_header.csv', delimiter=',', skiprows=1)
print("跳过表头:\n", loaded_header)

输出:

从 CSV 加载:
 [[1. 2. 3.]
 [4. 5. 6.]]
跳过表头:
 [[1. 2. 3.]
 [4. 5. 6.]]

2.2 np.genfromtxt - 处理复杂文本文件

genfromtxtloadtxt 更强大,可以处理缺失值、自动检测数据类型、从多个来源读取等。

# 准备一个带缺失值的文本文件(例如 missing_data.csv)
# 内容:
# 1,2,3
# 4,,6
# 7,8,9

# 使用 genfromtxt 加载,缺失值变为 NaN
data = np.genfromtxt('missing_data.csv', delimiter=',', missing_values='', filling_values=np.nan)
print("带缺失值的数据:\n", data)

# 还可以指定列的数据类型
data_with_dtype = np.genfromtxt('data.csv', delimiter=',', dtype=None, names=True)
# 如果第一行是列名,设置 names=True 会将其作为字段名
print("结构化数组:\n", data_with_dtype)

输出:

带缺失值的数据:
 [[ 1.  2.  3.]
 [ 4. nan  6.]
 [ 7.  8.  9.]]
结构化数组:
 [(1., 2., 3.) (4., 5., 6.)]

3. 内存映射文件

对于超大数组(无法一次性装入内存),NumPy 提供了内存映射文件 np.memmap。它允许将磁盘上的大文件当作数组访问,数据按需加载到内存。

# 创建一个内存映射数组(写入模式)
mmap = np.memmap('large_array.dat', dtype='float32', mode='w+', shape=(1000, 1000))

# 像普通数组一样操作
mmap[0, :] = 1.0
mmap[:, 0] = 2.0

# 必须 flush 确保数据写入磁盘
mmap.flush()

# 以只读模式打开
mmap_read = np.memmap('large_array.dat', dtype='float32', mode='r', shape=(1000, 1000))
print(mmap_read[0, :5])   # 读取部分数据

输出:

[1. 1. 1. 1. 1.]

内存映射非常适合处理超出内存限制的大型数据集,但操作速度比内存数组慢。

4. 格式化保存与加载

除了上述基本函数,NumPy还提供了 np.fromfilenp.tofile 用于直接读写二进制数据(不带元数据),以及 np.fromstring 从字符串或缓冲区创建数组。

# tofile / fromfile 不保存形状和类型信息,需手动指定
arr = np.array([1, 2, 3])
arr.tofile('raw.bin')

# 加载时需要指定相同的数据类型和形状
loaded_raw = np.fromfile('raw.bin', dtype=int)
print("fromfile 加载:", loaded_raw)   # 但形状丢失了,需要 reshape

输出:

fromfile 加载: [1 2 3]

5. 注意事项与最佳实践

  • 二进制 vs 文本:二进制格式(.npy/.npz)更小、更快,且能精确恢复数组;文本格式便于查看和跨平台交换。
  • 文件扩展名:建议使用标准扩展名(.npy, .npz, .txt, .csv)以保持清晰。
  • 数据类型一致性:加载文本文件时,注意数据类型可能被自动推断,可以使用 dtype 参数指定。
  • 大文件:对于超大文件,考虑使用内存映射或分块处理。
  • 与 Pandas 配合:Pandas 的 read_csvto_csv 对于带标签的表格数据更方便,但底层也可调用 NumPy 函数。
提示: 保存多个数组时,np.savez_compressed 通常能显著减小文件体积,特别是对于具有重复模式的数据。加载 .npz 文件后,可以用 data.files 查看所有数组名。

总结

本章介绍了 NumPy 的文件输入输出功能,包括高效的二进制格式(save/load)、灵活的文本格式(savetxt/loadtxt/genfromtxt)以及处理超大数据集的内存映射。掌握这些工具,可以轻松地将数据持久化到磁盘,并在不同程序间共享。下一章我们将学习 NumPy 的结构化数组,用于处理混合类型的数据。