为什么当我用 30 个不同的键将数据分割成 30 个较小的数据帧时,hdf5 文件的大小会急剧增加

why does hdf5 file size increase dramatically when I segment the data to 30 smaller dataframes with 30 different keys

我有一系列包含大熊猫数据帧的 hdf5 文件。 一个典型的文件大约有 1000,000 行。我使用 complib='blosc',complevel=9 进行压缩。 原始 hdf5 文件保存为 1 个平面文件。

然后我尝试将数据帧逻辑分割为 30 个较小的数据帧,并将它们保存在具有 30 个不同密钥和相同压缩的同一个 hdf5 文件中。

令人震惊的问题是,具有 30 个较小数据帧的文件比平面文件大 40 倍。

flat hdf5文件保存如下:

dfx.to_hdf(file_name, key='opp',mode='a',complib='blosc',complevel=9, append=True)

分割后的hdf5文件保存如下:

for i in range(30): dfx_small[i].to_hdf(file_name,key='d'+str(i), mode='a',complib='blosc',complevel=9

我是不是做错了什么或者这个大小增加是预期的?

补充观察

我比较了生成的所有 hdf5 文件 1) 平面数据帧与 2) 30 块数据帧 - 当数据帧保存为 30 个较小的数据帧时,最大文件的大小似乎增加了 8 到 10 倍,同时较小的文件增加了 100 到 1000 倍在尺寸方面。 然后我尝试将 hdf5 文件保存为 30 个块,压缩和不压缩。似乎当将具有唯一键的多个数据帧放在同一个 hdf5 文件中时,压缩几乎不起作用。我已经尝试了所有具有相似结果的压缩选项。

保存包含多个数据集的 hdf5 文件时存在压缩错误。

我添加了问题 #45286

我创建了一些简单的测试,并发现了一些有趣的行为。

  1. 首先,我创建了一些数据来模仿您的描述并看到了 11x 文件大小从 1 个 DF 增加到 30 个 DF。所以,很明显有些事情正在发生......(你必须提供复制 40 倍增长的代码。)
  2. 接下来使用上面相同的数据帧我创建了 2 个未压缩的文件——我没有包括压缩参数:complib='blosc',complevel=9。正如预期的那样,未压缩的文件更大,但从 1 DF 到 30 DF 的增加要低得多(仅增加 65%)。

Pandas 结果

# of DFs Compression Size (MB)
1 Blosc-9 3.1
30 Blosc-9 33.5
1 No 24.8
30 No 54.8

因此,部分这种行为可能是由于多个较小数据帧的压缩差异造成的。 (可能压缩效果不佳。)此外,Pandas 使用“有趣”的模式来存储数据帧。如果您使用 HDF View 检查文件,数据看起来不像 table(就像您使用 PyTables 或 h5py 保存数据时看到的那样)。当您有多个数据帧时,可能会有重复的开销(但这只是一个推论)。

2021-01-08 更新:
您还可以使用 PyTablesh5py 保存为 HDF5 格式。 (Pandas 建立在 PyTables 之上。)出于好奇,我扩展了上面的示例以使用每个包编写 HDF5 文件。一个文件有一个数据集,另一个文件有 30 个数据集(模仿数据帧方法)。我也比较了压缩的效果。结果提供了有趣的见解。

PyTables 结果

# of DSs Compression Size (MB)
1 Blosc-9 0.14
30 Blosc-9 0.40
1 Zlib-9 0.08
30 Zlib-9 0.17
1 No 11.9
30 No 13.5

Pandas 对比 PyTables 观察

  1. 在所有情况下,使用 PyTables 编写的文件都比来自 Pandas 的相应文件小得多。
  2. 对于使用PyTables编写的文件,数据集的数量对文件大小的影响较小。
  3. 使用 PyTables 从 1->30 个数据集进行压缩的效果不如 Pandas(使用任一压缩库)。
  4. 与 Pandas 相比,PyTables 的压缩更有效(以百分比减少)。
  5. 结论:PyTables 压缩和 HDF5 技术运行良好,不是问题的根源。

h5py 结果

# of DSs Compression Size (MB)
1 Gzip-9 0.15
30 Gzip-9 0.40
1 No 11.9
30 No 11.9

Pandas vs h5py 观察

  1. 4个中有3个是h5py写的文件多 小于 Pandas 中的相应文件。唯一的 Pandas 较小的文件是1 DF的压缩文件。
  2. 对于h5py写的文件,datasets个数不影响 文件大小。
  3. 使用 h5py 从 1->30 个数据集进行压缩的效果微不足道(使用 gzip 库)。
  4. 使用 h5py 压缩更有效(以百分比减少)。

PyTables 与 h5py 观察结果

  1. 在大多数情况下,用 PyTables 编写的文件在大小上与 h5py 中的相应文件非常相似。唯一较小的 h5py 文件是包含 30 个数据集的未压缩文件。
  2. 对于使用h5py编写的未压缩文件,数据集的数量不影响文件大小。
  3. 对于使用 Pytables 编写的未压缩文件,增加数据集的数量会增加文件大小(此测试数据增加 14%)。

创建测试数据(作为列表):

import string

col1 = list(string.ascii_letters)
col2 = [ x for x in range(1,53)]
col3 = [ float(x) for x in range(1,53)]

ncopies = 18_000
nslices = 30
fact = len(col1)*ncopies//nslices
col_c = []; col_int = []; col_float = []
for i in range(ncopies):
    col_c.extend(col1)
    col_int.extend(col2)
    col_float.extend(col3)
    

写测试数据用Pandas:

import pandas as pd

dfx = pd.DataFrame({'col_c': col_c, 'col_int': col_int, 'col_float': col_float})
dfx.to_hdf('pd_file_1_blosc.h5',key='test_data',mode='a',complib='blosc',complevel=9)
dfx.to_hdf('pd_file_1_uc.h5',key='test_data',mode='a')        

for i in range(nslices):
    dfx_small = dfx[i*fact:(i+1)*fact]
    dfx_small.to_hdf('pd_file_30_blosc.h5',key=f'd{i:02}', mode='a',complib='blosc',complevel=9)
    dfx_small.to_hdf('pd_file_30_uc.h5',key=f'd{i:02}', mode='a')

为 PyTables 和 h5py 创建 NumPy recarray:

import numpy as np

arr_dt = np.dtype([ ('col_c', 'S1'), ('col_int', int), ('col_float', float) ]) 
recarr = np.empty(shape=(len(col_c),), dtype=arr_dt)
recarr['col_c'] = col_c
recarr['col_int'] = col_int
recarr['col_float'] = col_float   

用PyTables写测试数据:

import tables as tb

f_blosc = tb.Filters(complib ="blosc", complevel=9)
f_zlib  = tb.Filters(complib ="zlib", complevel=9)

with tb.File('tb_file_1_blosc.h5','w') as h5f:
    h5f.create_table('/','test_data', obj=recarr, filters=f_blosc)
   
with tb.File('tb_file_1_zlib.h5','w') as h5f:
    h5f.create_table('/','test_data', obj=recarr, filters=f_zlib)
    
with tb.File('tb_file_1_uc.h5','w') as h5f:
    h5f.create_table('/','test_data', obj=recarr)
    
with tb.File('tb_file_30_blosc.h5','w') as h5f:
    for i in range(nslices):
        h5f.create_table('/',f'test_data_{i:02}', obj=recarr[i*fact:(i+1)*fact],
                           filters=f_blosc)

with tb.File('tb_file_30_zlib.h5','w') as h5f:
    for i in range(nslices):
        h5f.create_table('/',f'test_data_{i:02}', obj=recarr[i*fact:(i+1)*fact],
                           filters=f_zlib)
        
with tb.File('tb_file_30_uc.h5','w') as h5f:
    for i in range(nslices):
        h5f.create_table('/',f'test_data_{i:02}', obj=recarr[i*fact:(i+1)*fact])

用h5py写测试数据:

import h5py

with h5py.File('h5py_file_1_gzip.h5','w') as h5f:
    h5f.create_dataset('test_data', data=recarr, compression="gzip", compression_opts=9)
   
with h5py.File('h5py_file_1_uc.h5','w') as h5f:
    h5f.create_dataset('test_data', data=recarr)
    
with h5py.File('h5py_file_30_gzip.h5','w') as h5f:
    for i in range(nslices):
        h5f.create_dataset(f'test_data_{i:02}', data=recarr[i*fact:(i+1)*fact],
                           compression="gzip", compression_opts=9)
        
with h5py.File('h5py_file_30_uc.h5','w') as h5f:
    for i in range(nslices):
        h5f.create_dataset(f'test_data_{i:02}', data=recarr[i*fact:(i+1)*fact])

我之前的回答比较了 Pandas、PyTables 和 h5py 基于压缩和数据帧数创建的文件大小。从 Pandas 写入时还有另一个选项需要考虑:format=。默认值为 format='fixed',可选择使用 format='table'。 Pandas 文档对这 2 种格式进行了说明:

'fixed':固定格式。快writing/reading。不可附加,也不可搜索。

'table': Table 格式。编写为 PyTables Table 结构,其性能可能更差,但允许更灵活的操作,例如搜索/选择数据子集。

Pandas 我之前的回答中的测试都是 运行 默认值 ('fixed')。我 运行 额外的测试来比较 'fixed' 和 'table' 之间的文件大小。如果您不需要固定格式,结果很有希望。 table 格式的文件大小接近直接从 PyTables 写入时创建的文件大小(不足为奇)。

Pandas 结果:固定与 Table 格式

# of DFs Compression Fixed - Size (MB) Table - Size (MB)
1 Blosc-9 3.1 0.72
30 Blosc-9 33.5 1.2
1 Zlib-9 0.64
30 Zlib-9 1.1
1 No 24.8 23.4
30 No 54.8 23.9

这可能是固定格式与 table 格式使用不同架构的结果。固定格式将每个键的数据放入多个数据集中。相反,tables 格式只创建一个 table 数据。 (下面显示比较的图像。)当只有一个键时,这并不重要。然而,压缩是在数据集级别完成的,因此使用 fixed 时会出现更多的小对象,从而降低压缩它们的效率。

固定格式架构

Table 格式架构