百GB级Pandas(numpy ndarray)数据:部分加载与增量修改方案
numpy ndarray类型DataFrame的Parquet存储问题解决方案
1. 写入效率极低问题
逐行用df.loc插入是Pandas最常见的性能误区,和Parquet本身的写入性能无关:Pandas底层依赖连续内存块存储数据,每次逐行插入都会触发全量DataFrame的内存重分配和拷贝,行数越高单次插入的耗时越长,你观察到的「i越大单步插入越慢」就是这个原因。
解决方式是彻底放弃逐行插入的写法,按列批量生成所有数据后一次性构造DataFrame,同数据规模下耗时可以从8分钟降到10秒以内:
import numpy as np import pandas as pd n_rows = 100000 vec_len = 2000 # 按列批量生成所有ndarray,不要逐行拼接 data = { "a": [np.random.rand(vec_len) for _ in range(n_rows)], "b": [np.random.rand(vec_len) for _ in range(n_rows)] } # 一次性构造DataFrame并设置索引 x = pd.DataFrame(data, index=[f"t{i}" for i in range(n_rows)]) x.to_parquet("test.parquet")
如果总数据量大到无法一次性加载到内存,就分块生成数据,调用pyarrow.parquet.ParquetWriter逐块写入Parquet文件即可,不需要攒全量数据。
2. 无法按索引按需读取单行问题
原生pd.read_parquet仅支持列裁剪,要实现不加载全量文件的行级点查,需要基于Parquet的行组元数据做过滤下推,步骤如下:
- 写入Parquet时主动保留索引列,设置合理的行组大小(建议单组1~2万行),Parquet会自动为每个行组的列生成min/max统计信息
- 读取时先加载Parquet文件的轻量元数据,通过索引列的统计值定位目标行所在的行组,仅读取对应行组的数据再过滤目标行,全程不需要加载全文件
参考实现代码:
import pyarrow.parquet as pq # 写入时指定行组大小,保留索引 x.to_parquet("test.parquet", row_group_size=10000, index=True) pf = pq.ParquetFile("test.parquet") target_idx = "t123" target_row = None # 遍历行组元数据,定位目标索引所在的行组 for rg_idx in range(pf.metadata.num_row_groups): rg_meta = pf.metadata.row_group(rg_idx) # 读取索引列的统计值,判断目标索引是否在当前行组范围内 idx_col_meta = rg_meta.column(2) # 索引列位置根据实际列顺序调整 min_idx = idx_col_meta.statistics.min max_idx = idx_col_meta.statistics.max if min_idx <= target_idx <= max_idx: # 仅读取目标行组,过滤得到目标行 rg_df = pf.read_row_group(rg_idx).to_pandas() target_row = rg_df.loc[target_idx] break
如果点查频率很高,可以在写入时给索引列开启布隆过滤器,不需要遍历所有行组元数据就能定位目标位置,点查延迟可以降到百毫秒级别。
3. 增量修改必须重写全文件问题
Parquet本身是不可变列式存储格式,设计上不支持单文件内的原地修改,不存在直接修改单个100GB Parquet文件、只写增量部分的方案。针对大文件增量修改的需求,用分区存储的思路规避全量重写:
- 不要把所有数据存成单个大Parquet文件,按索引规则拆分成多个独立的小Parquet分片,比如每10万行存一个分片,单分片大小控制在1~2GB
- 新增、修改单行时,先定位到该行对应的分片,仅重写这一个1~2GB的分片即可,其余分片完全不需要改动
- 频繁小批量写入时,先把新增数据写到临时增量分片,攒到1GB以上的体量再和对应范围的旧分片合并,避免产生过多碎片文件
如果增删改操作非常频繁,可以直接用基于Parquet实现的表格式组件,内置了增量写入、版本管理、小文件合并能力,不需要自己实现分片逻辑。
4. 无法存储多维numpy ndarray问题
你遇到的二维、三维数组写入报错,本质是Parquet原生类型体系没有对多维定长数组的支持,pandas和pyarrow的默认序列化逻辑不会自动处理多维数组的转换,硬存会抛类型错误。根据你的场景选对应方案即可:
- 如果数组维度固定:写入时把多维数组展平成1维,额外新增列存储每个数组的shape信息,读取后调用
reshape还原即可,性能损耗极低 - 如果数组维度不固定、多维数组占比高:不要硬适配Parquet,Parquet的设计目标是结构化表数据,不是多维数组存储。直接换用Zarr或者HDF5格式,二者原生支持任意维度的numpy数组存储,支持分块读写、压缩、任意切片按需读取,和numpy的适配性远好于Parquet,100GB级别的场景性能表现稳定。其中Zarr为目录式存储,天生支持并行读写和增量修改,适配成本最低。
内容的提问来源于stack exchange,提问作者Basj
相关产品推荐
相关产品推荐

