使用pandas将CSV文件转换为HDF5
当我使用pandas把csv文件转换成hdf5文件时,生成的文件非常大。例如,一个测试用的csv文件(有23列,130万行),大小是170Mb,但转换后得到的hdf5文件却达到了2Gb。不过,如果直接用pytables写hdf5文件,大小只有20Mb。在下面的代码中(用于在pandas中进行转换),数据框中的对象列的值被明确转换为字符串对象(以防止序列化):
# Open the csv file as pandas data frame
data = pd.read_csv(csvfilepath, sep=delimiter, low_memory=False)
# Write the resulting data frame to the hdf5 file
data.to_hdf(hdf5_file_path, table_name, format='table', complevel=9,
complib='lzo')
这是用vitables检查的hdf5文件:

让我觉得奇怪的是,值的表示方式是以(python?)列表的形式,数据类型是(values_block0:int, values_block1:float 和 values_block2:string),而不是每个csv文件中的每一列都有一个特定的列。我在想这是否导致了文件大小的增加,以及这对查询时间会有什么影响?
考虑到大约有1Tb的数据需要转换,我想知道有什么办法可以减少生成的hdf5文件的大小?
附注: 我知道这个问题,但它提到的大文件大小是由于HDF5格式本身造成的,而在这种情况下,直接绕过pandas得到的hdf5文件要小得多,这不应该是原因。
再附注: 使用data.iloc而不是data.loc,正如joris建议的那样,并没有什么区别。我已经去掉了“转换”,这也没有影响。根据Jeff的要求,关于读取的数据框的信息:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1303331 entries, 0 to 1303330
Columns: 23 entries, _PlanId to ACTIVITY_Gratis
dtypes: float64(1), int64(5), object(17)
1 个回答
这里是对各种输入输出方法的时间和大小的非正式比较。
使用的是0.13.1版本,运行在64位的Linux系统上。
设置
In [3]: N = 1000000
In [4]: df = DataFrame(dict([ ("int{0}".format(i),np.random.randint(0,10,size=N)) for i in range(5) ]))
In [5]: df['float'] = np.random.randn(N)
In [6]: from random import randrange
In [8]: for i in range(10):
...: df["object_1_{0}".format(i)] = ['%08x'%randrange(16**8) for _ in range(N)]
...:
In [9]: for i in range(7):
...: df["object_2_{0}".format(i)] = ['%15x'%randrange(16**15) for _ in range(N)]
...:
In [11]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000000 entries, 0 to 999999
Data columns (total 23 columns):
int0 1000000 non-null int64
int1 1000000 non-null int64
int2 1000000 non-null int64
int3 1000000 non-null int64
int4 1000000 non-null int64
float 1000000 non-null float64
object_1_0 1000000 non-null object
object_1_1 1000000 non-null object
object_1_2 1000000 non-null object
object_1_3 1000000 non-null object
object_1_4 1000000 non-null object
object_1_5 1000000 non-null object
object_1_6 1000000 non-null object
object_1_7 1000000 non-null object
object_1_8 1000000 non-null object
object_1_9 1000000 non-null object
object_2_0 1000000 non-null object
object_2_1 1000000 non-null object
object_2_2 1000000 non-null object
object_2_3 1000000 non-null object
object_2_4 1000000 non-null object
object_2_5 1000000 non-null object
object_2_6 1000000 non-null object
dtypes: float64(1), int64(5), object(17)
types: float64(1), int64(5), object(17)
使用不同的方法保存数据
In [12]: df.to_hdf('test_fixed.h5','data',format='fixed')
In [13]: df.to_hdf('test_table_no_dc.h5','data',format='table')
In [14]: df.to_hdf('test_table_dc.h5','data',format='table',data_columns=True)
In [15]: df.to_hdf('test_fixed_compressed.h5','data',format='fixed',complib='blosc',complevel=9)
!ls -ltr *.h5
In [16]: !ls -ltr *.h5
-rw-rw-r-- 1 jreback users 361093304 Apr 28 09:20 test_fixed.h5
-rw-rw-r-- 1 jreback users 311475690 Apr 28 09:21 test_table_no_dc.h5
-rw-rw-r-- 1 jreback users 351316525 Apr 28 09:22 test_table_dc.h5
-rw-rw-r-- 1 jreback users 317467870 Apr 28 2014 test_fixed_compressed.h5
磁盘上的文件大小取决于每一列选择的字符串大小。如果你没有使用数据列,那么文件大小就是所有字符串中最长的那个。因此,使用数据列可能会影响文件大小(不过因为你有更多的列,所以每列占用的空间会更多)。你可能想要指定 min_item_size
来控制这个大小,具体可以查看这里。
下面是一个磁盘上结构的示例:
In [8]: DataFrame(dict(A = ['foo','bar','bah'], B = [1,2,3], C = [1.0,2.0,3.0], D=[4.0,5.0,6.0])).to_hdf('test.h5','data',mode='w',format='table')
In [9]: !ptdump -avd test.h5
/ (RootGroup) ''
/._v_attrs (AttributeSet), 4 attributes:
[CLASS := 'GROUP',
PYTABLES_FORMAT_VERSION := '2.1',
TITLE := '',
VERSION := '1.0']
/data (Group) ''
/data._v_attrs (AttributeSet), 14 attributes:
[CLASS := 'GROUP',
TITLE := '',
VERSION := '1.0',
data_columns := [],
encoding := None,
index_cols := [(0, 'index')],
info := {1: {'type': 'Index', 'names': [None]}, 'index': {}},
levels := 1,
nan_rep := 'nan',
non_index_axes := [(1, ['A', 'B', 'C', 'D'])],
pandas_type := 'frame_table',
pandas_version := '0.10.1',
table_type := 'appendable_frame',
values_cols := ['values_block_0', 'values_block_1', 'values_block_2']]
/data/table (Table(3,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
"values_block_1": Int64Col(shape=(1,), dflt=0, pos=2),
"values_block_2": StringCol(itemsize=3, shape=(1,), dflt='', pos=3)}
byteorder := 'little'
chunkshape := (1872,)
autoindex := True
colindexes := {
"index": Index(6, medium, shuffle, zlib(1)).is_csi=False}
/data/table._v_attrs (AttributeSet), 19 attributes:
[CLASS := 'TABLE',
FIELD_0_FILL := 0,
FIELD_0_NAME := 'index',
FIELD_1_FILL := 0.0,
FIELD_1_NAME := 'values_block_0',
FIELD_2_FILL := 0,
FIELD_2_NAME := 'values_block_1',
FIELD_3_FILL := '',
FIELD_3_NAME := 'values_block_2',
NROWS := 3,
TITLE := '',
VERSION := '2.7',
index_kind := 'integer',
values_block_0_dtype := 'float64',
values_block_0_kind := ['C', 'D'],
values_block_1_dtype := 'int64',
values_block_1_kind := ['B'],
values_block_2_dtype := 'string24',
values_block_2_kind := ['A']]
Data dump:
[0] (0, [1.0, 4.0], [1], ['foo'])
[1] (1, [2.0, 5.0], [2], ['bar'])
[2] (2, [3.0, 6.0], [3], ['bah'])
数据类型被分组到块中(如果你使用了数据列,它们会是分开的)。这些只是以这种方式打印出来;实际上它们是以数组的形式存储的。