如何将含Numpy ndarray的Pandas DataFrame序列化存储到HDF5?
问题说明
在对包含二维/三维NumPy ndarray的Pandas DataFrame执行HDF5序列化存储时,运行以下代码会触发报错:
import numpy as np, pandas as pd store = pd.HDFStore("store.h5") df = pd.DataFrame(columns=['a', 'b']) df.loc['t1'] = {'a': np.random.rand(100, 100), 'b': np.random.rand(2000)} store.append('test', df) store.close()
抛出的错误信息为:
TypeError: Cannot serialize the column [a] because its data contents are not [string] but [mixed] object dtype
核心需求如下:
- 支撑百GB级数据存储
- 支持仅加载局部数据,小幅修改时无需重写整个文件
- 适配形状可变的稀疏n维数组/张量存储
- 支持带标签索引的多维数组存取
原因说明
Pandas HDFStore的table存储格式(唯一支持增量追加、条件查询、局部写入的格式)基于PyTables实现,仅能序列化标量值、定长一维数组类型的列,当列中存储二维及以上ndarray时,整列会被识别为混合object类型,直接触发上述序列化错误。如果使用fixed固定格式存储,虽然可以直接存入object类型的多维数组,但不支持增量写入、条件查询、局部修改,完全无法满足百GB级数据的使用需求。
可行方案
方案1:基于h5py原生API自定义存储结构(完全匹配所有需求,推荐百GB级场景使用)
直接使用h5py操作HDF5文件,绕开Pandas的类型限制,自定义存储结构:
- 每个多维数组单独存为HDF5数据集,创建数据集时通过
maxshape参数设置可变维度,支持后续任意调整形状、局部写入修改,修改单个数据集不会重写整个文件 - 单独创建一个元信息表,存储每个数组对应的标签索引、存储路径、形状、数据类型等信息,模拟Pandas的标签索引能力
- 存储时开启HDF5自带的gzip/lzf压缩,可大幅降低稀疏数组的空间占用
简单实现示例:
import h5py import numpy as np class NDArrayHDFStore: def __init__(self, path): self.f = h5py.File(path, 'a') # 初始化元信息表 if 'meta' not in self.f: meta_dt = h5py.string_dtype(encoding='utf-8') self.f.create_dataset('meta', shape=(0,), maxshape=(None,), dtype=meta_dt) def put(self, table_key, row_label, arr): # 存储数组 ds_path = f'data/{table_key}/{row_label}' if ds_path in self.f: del self.f[ds_path] self.f.create_dataset(ds_path, data=arr, compression='lzf') # 更新元信息 meta_len = self.f['meta'].shape[0] self.f['meta'].resize(meta_len+1, axis=0) self.f['meta'][meta_len] = f'{table_key},{row_label},{ds_path},{arr.shape},{arr.dtype}' def get(self, table_key, row_label): ds_path = f'data/{table_key}/{row_label}' return np.array(self.f[ds_path]) def close(self): self.f.close() # 使用示例 store = NDArrayHDFStore("custom_store.h5") store.put('test', 't1', np.random.rand(100,100)) arr = store.get('test', 't1') store.close()
方案2:数组序列化包装(仅适合小数据量轻量场景)
如果必须使用Pandas HDFStore接口,可以提前将多维数组序列化为字节串存入DataFrame,读取时再反序列化:
import pickle import numpy as np import pandas as pd def arr2bytes(arr): return pickle.dumps(arr, protocol=pickle.HIGHEST_PROTOCOL) def bytes2arr(b): return pickle.loads(b) # 存储 store = pd.HDFStore("store.h5", mode='w') df = pd.DataFrame(columns=['a', 'b']) df.loc['t1'] = {'a': arr2bytes(np.random.rand(100,100)), 'b': arr2bytes(np.random.rand(2000))} store.append('test', df, format='table') store.close() # 读取 store = pd.HDFStore("store.h5", mode='r') df = store['test'] df[['a','b']] = df[['a','b']].applymap(bytes2arr) store.close()
注意:该方案存储的数组无法在HDF5层面做局部读写,必须将对应行/列的字节串全量加载到内存反序列化后才能操作,百GB级场景下性能极差,不推荐使用。
方案3:Zarr + Xarray替代方案(张量场景优先选择)
如果不强绑定HDF5格式,Zarr是更适配n维数组存储的方案:
- 原生支持分块存储,局部读写时仅操作对应分块,不会重写整个文件,百GB级数据读写性能远高于Pandas封装的HDF5
- 原生支持可变形状数组、稀疏数组存储,压缩效率更高
- 搭配Xarray可以直接实现带标签的多维数组存取,索引逻辑和Pandas完全兼容,学习成本低
内容的提问来源于stack exchange,提问作者Basj
相关产品推荐
相关产品推荐

