NumPy 数组 I/O 操作完全指南:从基础到高级应用

在数据科学和机器学习领域,NumPy 作为 Python 的核心科学计算库,其数组操作的高效性广为人知。然而,在实际项目中,我们不仅需要在内存中操作数组,还需要将数组持久化保存到磁盘,或者从磁盘加载预处理好的数据。本文将全面探讨 NumPy 数组的各种 I/O 操作方法,涵盖从简单的文本格式到高效的二进制格式,再到处理超大规模数据的内存映射技术。

一、文本文件 I/O 操作

1.1 保存数组到文本文件

NumPy 提供了 savetxt() 函数用于将数组保存为文本文件,这是最直观的数据交换格式:

import numpy as np

# 创建一个示例数组
data = np.array([[1.12, 2.34, 3.56], 
                 [4.67, 5.78, 6.89],
                 [7.91, 8.02, 9.13]])

# 保存为文本文件
np.savetxt('data.txt', data, delimiter=',', fmt='%.2f', header='X,Y,Z', comments='# ')

参数解析

  • delimiter:指定列分隔符,默认为空格

  • fmt:格式字符串,控制数值的显示格式

  • header:文件开头的描述性文字

  • comments:注释符号,用在 header 前面

文件内容示例

# X,Y,Z
1.12,2.34,3.56
4.67,5.78,6.89
7.91,8.02,9.13

1.2 从文本文件加载数组

对应的加载函数是 loadtxt()

# 从文本文件加载数据
loaded_data = np.loadtxt('data.txt', delimiter=',')

# 可以跳过首行标题
loaded_data = np.loadtxt('data.txt', delimiter=',', skiprows=1)

高级用法

  • 指定数据类型:dtype=np.float32

  • 选择加载特定列:usecols=(0, 2)

  • 处理缺失值:missing_values='NA', filling_values=0

1.3 文本格式的优缺点分析

优点

  • 人类可读,便于调试和检查

  • 通用性强,几乎所有工具都能处理文本文件

  • 版本兼容性好,不受 NumPy 版本影响

缺点

  • 存储效率低,文件体积大

  • 读写速度慢,特别是大数据量时

  • 精度可能丢失,取决于格式化字符串

适用场景:小型数据集交换、调试阶段、需要人工检查的数据

二、二进制文件 I/O 操作

2.1 .npy 格式(单个数组)

.npy 是 NumPy 专用的二进制格式,针对数组存储进行了优化。

保存单个数组

np.save('single_array.npy', data)

加载单个数组

array_data = np.load('single_array.npy')

文件特点

  • 自动保存数组的 dtype、shape 等元信息

  • 支持所有 NumPy 数据类型

  • 文件头包含格式说明,具有版本控制

2.2 .npz 格式(多个数组)

.npz 是压缩的存档格式,可以保存多个数组。

保存多个数组

arr1 = np.arange(10)
arr2 = np.random.rand(5,5)
np.savez('multiple_arrays.npz', array1=arr1, array2=arr2)

加载多个数组

with np.load('multiple_arrays.npz') as data:
    arr1 = data['array1']
    arr2 = data['array2']

压缩版本(节省空间):

np.savez_compressed('compressed_arrays.npz', array1=arr1, array2=arr2)

2.3 二进制格式的性能优势

我们通过实验对比文本和二进制格式的性能差异:

import time

large_array = np.random.rand(10000, 10000)

# 文本格式
start = time.time()
np.savetxt('large.txt', large_array)
print(f"文本保存时间: {time.time()-start:.2f}s")

start = time.time()
_ = np.loadtxt('large.txt')
print(f"文本加载时间: {time.time()-start:.2f}s")

# 二进制格式
start = time.time()
np.save('large.npy', large_array)
print(f"二进制保存时间: {time.time()-start:.2f}s")

start = time.time()
_ = np.load('large.npy')
print(f"二进制加载时间: {time.time()-start:.2f}s")

典型结果

文本保存时间: 45.32s
文本加载时间: 38.76s
二进制保存时间: 1.02s
二进制加载时间: 0.25s

二进制格式在速度和存储空间上通常有数量级的优势。

三、内存映射文件技术

3.1 内存映射的基本原理

内存映射(memory mapping)允许将磁盘上的文件直接映射到内存地址空间,实现以下优势:

  • 处理超过物理内存限制的大型数组

  • 多个进程共享同一数据

  • 惰性加载,只在访问时读取相应数据

3.2 创建内存映射数组

# 创建一个新的内存映射数组
shape = (100000, 100000)  # 约74.5GB的float64数组
mmap_arr = np.memmap('huge_array.dat', dtype='float64', mode='w+', shape=shape)

# 分段写入数据
for i in range(0, shape[0], 10000):
    mmap_arr[i:i+10000] = np.random.rand(10000, shape[1])
    
# 确保数据写入磁盘
del mmap_arr

3.3 读取现有内存映射

# 以只读模式打开现有文件
mmap_arr = np.memmap('huge_array.dat', dtype='float64', mode='r', shape=shape)

# 计算每列平均值
column_means = np.mean(mmap_arr, axis=0)

3.4 内存映射的高级用法

模式选择

  • 'r':只读

  • 'r+':读写(文件必须已存在)

  • 'w+':创建或覆盖

  • 'c':写时复制

跨进程共享

# 进程1写入数据
mmap = np.memmap('shared.dat', dtype='float32', mode='w+', shape=(1000,))
mmap[:] = np.random.rand(1000)

# 进程2读取数据
mmap2 = np.memmap('shared.dat', dtype='float32', mode='r', shape=(1000,))
print(mmap2[0])  # 访问相同数据

四、其他专业格式接口

4.1 HDF5 格式

HDF5 是科学计算中常用的层次化数据格式,需要安装 h5py 包:

import h5py

with h5py.File('data.h5', 'w') as f:
    # 创建数据集
    f.create_dataset('training/data', data=train_data)
    f.create_dataset('training/labels', data=train_labels)
    # 添加属性
    f['training/data'].attrs['description'] = 'Input features'
    
with h5py.File('data.h5', 'r') as f:
    # 读取数据
    data = f['training/data'][:]
    # 读取属性
    desc = f['training/data'].attrs['description']

4.2 Pandas 交互

NumPy 数组与 Pandas DataFrame 的相互转换:

import pandas as pd

# NumPy 到 DataFrame
df = pd.DataFrame(data, columns=['X', 'Y', 'Z'])

# DataFrame 到 NumPy
array = df.values  # 或 df.to_numpy()

4.3 图像数据

使用 imageio 或 OpenCV 处理图像数据:

import imageio

# 读取图像为 NumPy 数组
img = imageio.imread('image.jpg')  # 形状为 (height, width, channels)

# 保存数组为图像
imageio.imwrite('output.png', img_array)

五、性能优化与实践建议

5.1 格式选择决策树

  1. 数据量小且需要人工检查? → 文本格式

  2. 单个大型数组? → .npy 格式

  3. 多个相关数组? → .npz 格式

  4. 数据量超过内存? → 内存映射

  5. 复杂层次化数据? → HDF5 格式

5.2 最佳实践

  1. 版本控制:保存数据时记录 NumPy 版本

    np.savez('data.npz', array=data, 
             metadata={'numpy_version': np.__version__})
  2. 数据验证:加载后检查形状和 dtype

    assert loaded_array.shape == expected_shape
    assert loaded_array.dtype == np.float32
  3. 安全考虑allow_pickle=False 避免潜在的安全风险

    np.load('data.npy', allow_pickle=False)
  4. 压缩策略:权衡压缩率和速度

    # 快速但压缩率低
    np.savez('fast.npz', data=data)
    # 慢但压缩率高
    np.savez_compressed('small.npz', data=data)

六、总结

NumPy 提供了丰富多样的 I/O 操作方法,从简单的文本格式到高效的二进制格式,再到处理超大规模数据的内存映射技术。在实际项目中,我们应该根据数据规模、使用场景和性能需求选择合适的存储格式:

  • 小型临时数据:文本格式便于调试

  • 中型生产数据:.npy/.npz 提供最佳性能

  • 超大型数据集:内存映射技术突破内存限制

  • 复杂结构化数据:考虑 HDF5 等专业格式

掌握这些 I/O 技术将大大提高数据处理的效率和灵活性,是每个数据科学家和工程师必备的核心技能。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值