如何高效持久化引用同一DataFrame的多个切片?
高效持久化DataFrame切片列表的方案
针对你的需求——既要避免pickle存储时的重复数据膨胀,又要保证切片的快速访问,以下是几个实用方案:
方案1:NumPy数组+切片索引列表
将原DataFrame的数值数据转为NumPy数组(保留时间索引),持久化数组和切片的起止索引。由于NumPy切片访问的原生速度远快于pandas的iloc,能大幅提升访问效率,同时磁盘占用仅为原数据+索引列表,无重复数据。
实现代码
import pandas as pd import numpy as np import pickle # 生成原数据 timestamps = pd.date_range("2018-01-01", "2023-03-29", freq="H") values = np.random.rand(len(timestamps)) score_df = pd.DataFrame({"timestamp": timestamps, "value": values}).set_index("timestamp") # 转换为NumPy数组,保留时间索引 values_array = score_df["value"].to_numpy() # 预计算每个切片的起止索引,将slice对象转为(start, stop)元组方便持久化 slice_indices = [] for date in timestamps: start, stop = score_df.index.slice_indexer(date, date + pd.Timedelta(days=1000)) slice_indices.append((start, stop)) # 持久化:存储数组、时间索引、切片索引 with open("optimized_data.pkl", "wb") as f: pickle.dump((values_array, timestamps, slice_indices), f) # 加载后快速访问切片 with open("optimized_data.pkl", "rb") as f: loaded_array, loaded_timestamps, loaded_slices = pickle.load(f) # 快速获取指定切片的DataFrame def get_slice(idx): start, stop = loaded_slices[idx] slice_values = loaded_array[start:stop] slice_index = loaded_timestamps[start:stop] return pd.DataFrame({"value": slice_values}, index=slice_index) # 测试访问速度 %timeit get_slice(999)
优势
- 磁盘占用极小:仅存储原数据一次+切片索引列表,无重复数据
- 访问速度接近原生列表:NumPy切片是O(1)时间定位,构造DataFrame的开销远小于
score_df.iloc - 实现简单,无需额外依赖
方案2:HDF5内存映射原数据+切片索引存储
利用HDF5的高效存储和内存映射特性,将原DataFrame以内存映射模式存储,同时预存切片索引。访问时直接通过索引定位内存映射中的数据,无需重复加载整个DataFrame。
实现代码
import pandas as pd import numpy as np import h5py # 生成原数据(同前) timestamps = pd.date_range("2018-01-01", "2023-03-29", freq="H") values = np.random.rand(len(timestamps)) score_df = pd.DataFrame({"timestamp": timestamps, "value": values}).set_index("timestamp") # 预计算切片索引 slice_indices = [] for date in timestamps: start, stop = score_df.index.slice_indexer(date, date + pd.Timedelta(days=1000)) slice_indices.append((start, stop)) # 存储原数据到HDF5(内存映射模式) score_df.to_hdf("score_data.h5", key="original_data", mode="w", format="table") # 存储切片索引 with h5py.File("slice_indices.h5", "w") as f: f.create_dataset("indices", data=np.array(slice_indices, dtype=np.int64)) # 加载时启用内存映射 loaded_df = pd.read_hdf("score_data.h5", key="original_data", mode="r") with h5py.File("slice_indices.h5", "r") as f: loaded_slices = f["indices"][:] # 快速访问切片 def get_slice(idx): start, stop = loaded_slices[idx] return loaded_df.iloc[start:stop] # 测试访问速度 %timeit get_slice(999)
优势
- 内存占用低:内存映射模式下,数据不会全部加载到内存,仅访问时读取对应块
- 访问速度显著优于直接
iloc:HDF5的table格式支持快速范围查询 - 支持增量更新切片索引,无需重新存储原数据
方案3:预生成切片并以Parquet分块存储
如果磁盘空间允许,可预生成所有切片,以Parquet格式按索引存储(每个切片对应一个文件)。Parquet的高压缩率能降低重复数据的磁盘占用,同时读取速度极快。
实现代码
import pandas as pd import numpy as np import os # 生成原数据(同前) timestamps = pd.date_range("2018-01-01", "2023-03-29", freq="H") values = np.random.rand(len(timestamps)) score_df = pd.DataFrame({"timestamp": timestamps, "value": values}).set_index("timestamp") # 创建存储目录 os.makedirs("slice_parquets", exist_ok=True) # 预生成并存储每个切片 for idx, date in enumerate(timestamps): start, stop = score_df.index.slice_indexer(date, date + pd.Timedelta(days=1000)) slice_df = score_df.iloc[start:stop] slice_df.to_parquet(f"slice_parquets/slice_{idx}.parquet") # 快速读取切片 def get_slice(idx): return pd.read_parquet(f"slice_parquets/slice_{idx}.parquet") # 测试访问速度 %timeit get_slice(999)
优势
- 读取速度最快:直接读取预生成的切片文件,无需计算索引
- Parquet压缩率高:即使切片有重叠,磁盘占用也远低于pickle
- 支持并行读取和分布式访问(适合大数据场景)
方案对比
| 方案 | 磁盘占用 | 访问速度 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| NumPy数组+索引 | 极小 | 极快 | 低 | 中小数据量,追求极致性能 |
| HDF5内存映射 | 小 | 快 | 中 | 大数据量,内存有限 |
| Parquet分块存储 | 中等 | 最快 | 低 | 对读取速度要求极高,磁盘充足 |
内容的提问来源于stack exchange,提问作者Semi
相关产品推荐
相关产品推荐

