为什么当我用 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
我创建了一些简单的测试,并发现了一些有趣的行为。
- 首先,我创建了一些数据来模仿您的描述并看到了 11x
文件大小从 1 个 DF 增加到 30 个 DF。所以,很明显有些事情正在发生......(你必须提供复制 40 倍增长的代码。)
- 接下来使用上面相同的数据帧我创建了 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 更新:
您还可以使用 PyTables
或 h5py
保存为 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 观察
- 在所有情况下,使用 PyTables 编写的文件都比来自 Pandas 的相应文件小得多。
- 对于使用PyTables编写的文件,数据集的数量对文件大小的影响较小。
- 使用 PyTables 从 1->30 个数据集进行压缩的效果不如 Pandas(使用任一压缩库)。
- 与 Pandas 相比,PyTables 的压缩更有效(以百分比减少)。
- 结论: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 观察
- 4个中有3个是h5py写的文件多
小于 Pandas 中的相应文件。唯一的 Pandas
较小的文件是1 DF的压缩文件。
- 对于h5py写的文件,datasets个数不影响
文件大小。
- 使用 h5py 从 1->30 个数据集进行压缩的效果微不足道(使用 gzip 库)。
- 使用 h5py 压缩更有效(以百分比减少)。
PyTables 与 h5py 观察结果
- 在大多数情况下,用 PyTables 编写的文件在大小上与 h5py 中的相应文件非常相似。唯一较小的 h5py 文件是包含 30 个数据集的未压缩文件。
- 对于使用h5py编写的未压缩文件,数据集的数量不影响文件大小。
- 对于使用 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 格式架构
我有一系列包含大熊猫数据帧的 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
我创建了一些简单的测试,并发现了一些有趣的行为。
- 首先,我创建了一些数据来模仿您的描述并看到了 11x 文件大小从 1 个 DF 增加到 30 个 DF。所以,很明显有些事情正在发生......(你必须提供复制 40 倍增长的代码。)
- 接下来使用上面相同的数据帧我创建了 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 更新:
您还可以使用 PyTables
或 h5py
保存为 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 观察
- 在所有情况下,使用 PyTables 编写的文件都比来自 Pandas 的相应文件小得多。
- 对于使用PyTables编写的文件,数据集的数量对文件大小的影响较小。
- 使用 PyTables 从 1->30 个数据集进行压缩的效果不如 Pandas(使用任一压缩库)。
- 与 Pandas 相比,PyTables 的压缩更有效(以百分比减少)。
- 结论: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 观察
- 4个中有3个是h5py写的文件多 小于 Pandas 中的相应文件。唯一的 Pandas 较小的文件是1 DF的压缩文件。
- 对于h5py写的文件,datasets个数不影响 文件大小。
- 使用 h5py 从 1->30 个数据集进行压缩的效果微不足道(使用 gzip 库)。
- 使用 h5py 压缩更有效(以百分比减少)。
PyTables 与 h5py 观察结果
- 在大多数情况下,用 PyTables 编写的文件在大小上与 h5py 中的相应文件非常相似。唯一较小的 h5py 文件是包含 30 个数据集的未压缩文件。
- 对于使用h5py编写的未压缩文件,数据集的数量不影响文件大小。
- 对于使用 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 格式架构