【问题标题】:how to make pandas HDFStore 'put' operation faster如何使 pandas HDFStore 'put' 操作更快
【发布时间】:2012-12-30 13:54:38
【问题描述】:

我正在尝试使用 pandas、hdf5 构建一个 ETL 工具包。

我的计划是

  1. 从mysql中提取表到DataFrame;
  2. 将此DataFrame放入HDFStore;

但是当我执行第 2 步时,我发现将数据框放入 *.h5 文件会花费太多时间。

  • 源mysql服务器表大小:498MB
    • 52 列
    • 924,624 条记录
  • 将数据框放入后*.h5文件的大小:513MB
    • “放置”操作花费 849.345677137 秒

我的问题是:
这个时间成本正常吗?
有什么办法让它更快?


更新 1

谢谢杰夫

  • 我的代码很简单:

    extract_store = HDFStore('extract_store.h5')
    extract_store['df_staff'] = df_staff

  • 当我尝试 'ptdump -av file.h5' 时出现错误,但我仍然可以从这个 h5 文件加载数据框对象:

tables.exceptions.HDF5ExtError: HDF5 错误回溯

文件“../../../src/H5F.c”,第 1512 行,在 H5Fopen
无法在 H5F_open 中打开文件“../../../src/H5F.c”,第 1307 行
无法读取 H5F_super_read 中的超级块文件“../../../src/H5Fsuper.c”,第 305 行
无法在 H5F_locate_signature 中找到文件签名文件“../../../src/H5Fsuper.c”,第 153 行
找不到有效的文件签名

HDF5 错误回溯结束

无法打开/创建文件“extract_store.h5”

  • 其他一些信息:
    • 熊猫版本:'0.10.0'
    • 操作系统:ubuntu 服务器 10.04 x86_64
    • cpu: 8 * Intel(R) Xeon(R) CPU X5670 @ 2.93GHz
    • MemTotal:51634016 kB

我会将 pandas 更新到 0.10.1-dev 并重试。


更新 2

  • 我已将 pandas 更新为 '0.10.1.dev-6e2b6ea'
  • 但时间成本并没有降低,这次花费了 884.15 s 秒
  • “ptdump -av file.h5”的输出是:
/(根组)'' /._v_attrs (AttributeSet),4个属性: [类:='组', PYTABLES_FORMAT_VERSION := '2.0', 标题:='', 版本:='1.0'] /df_bugs (组) '' /df_bugs._v_attrs(属性集),12个属性: [类:='组', 标题:='', 版本 := '1.0', axis0_variety := '常规', axis1_variety := '常规', block0_items_variety := '常规', block1_items_variety := '常规', block2_items_variety := '常规', nblocks := 3, ndim := 2, pandas_type := '框架', pandas_version := '0.10.1'] /df_bugs/axis0 (Array(52,)) '' atom := StringAtom(itemsize=19, shape=(), dflt='') 主调:= 0 味道 := 'numpy' byteorder := '无关' 块状 := 无 /df_bugs/axis0._v_attrs(属性集),7个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 种类 := '字符串', 名称 := 无, 转置:=真] /df_bugs/axis1(数组(924624,))'' atom := Int64Atom(shape=(), dflt=0) 主调:= 0 味道 := 'numpy' 字节序 := '小' 块状 := 无 /df_bugs/axis1._v_attrs(属性集),7个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 种类 := '整数', 名称 := 无, 转置:=真] /df_bugs/block0_items (Array(5,)) '' atom := StringAtom(itemsize=12, shape=(), dflt='') 主调:= 0 味道 := 'numpy' byteorder := '无关' 块状 := 无 /df_bugs/block0_items._v_attrs(属性集),7个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 种类 := '字符串', 名称 := 无, 转置:=真] /df_bugs/block0_values (Array(924624, 5)) '' atom := Float64Atom(shape=(), dflt=0.0) 主调:= 0 味道 := 'numpy' 字节序 := '小' 块状 := 无 /df_bugs/block0_values._v_attrs(属性集),5个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 转置:=真] /df_bugs/block1_items (Array(19,)) '' atom := StringAtom(itemsize=19, shape=(), dflt='') 主调:= 0 味道 := 'numpy' byteorder := '无关' 块状 := 无 /df_bugs/block1_items._v_attrs(属性集),7个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 种类 := '字符串', 名称 := 无, 转置:=真] /df_bugs/block1_values (Array(924624, 19)) '' atom := Int64Atom(shape=(), dflt=0) 主调:= 0 味道 := 'numpy' 字节序 := '小' 块状 := 无 /df_bugs/block1_values._v_attrs(属性集),5个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 转置:=真] /df_bugs/block2_items (Array(28,)) '' atom := StringAtom(itemsize=18, shape=(), dflt='') 主调:= 0 味道 := 'numpy' byteorder := '无关' 块状 := 无 /df_bugs/block2_items._v_attrs(属性集),7个属性: [类:='阵列', 风味 := 'numpy', 标题:='', 版本 := '2.3', 种类 := '字符串', 名称 := 无, 转置:=真] /df_bugs/block2_values (VLArray(1,)) '' 原子 = 对象原子() byteorder = '无关' nrows = 1 风味='numpy' /df_bugs/block2_values._v_attrs(属性集),5个属性: [类:='VLARRAY', 伪原子 := '对象', 标题:='', 版本:='1.3', 转置:=真]
  • 我在下面尝试了您的代码(将数据帧放入 hdfstore 中,参数 'table' 为 True),但出现错误,似乎不支持 python 的数据时间类型:

异常:找不到正确的原子类型 -> [dtype->object] 对象 'datetime.datetime' 类型的没有 len()


更新 3

感谢杰夫。 抱歉耽搁了。

  • 表格。版本:'2.4.0'。
  • 是的,884 秒只是 put 操作的成本,没有来自 mysql 的 pull 操作
  • 一行数据框(df.ix[0]):
bug_id 1 分配给 185 bug_file_loc 无 bug_severity 严重 bug_status 关闭 创建_ts 1998-05-06 21:27:00 delta_ts 2012-05-09 14:41:41 short_desc 两个游标。 host_op_sys 未知 guest_op_sys 未知 优先级 P3 rep_platform IA32 记者56 product_id 7 类别 ID 983 组件 ID 12925 分辨率固定 目标里程碑ws1 qa_contact 412 status_whiteboard 票数 0 关键词 SR 上次差异 2012-05-09 14:41:41 曾经确认过 1 记者可访问 1 cclist_accessible 1 估计时间 0.00 剩余时间 0.00 截止日期 无 别名 无 found_in_product_id 0 found_in_version_id 0 found_in_phase_id 0 cf_type 缺陷 cf_reported_by 开发 cf_attempted NaN cf_failed NaN cf_public_summary cf_doc_impact 0 cf_security 0 cf_build NaN cf_branch cf_change NaN cf_test_id NaN cf_regression 未知 cf_reviewer 0 cf_on_hold 0 cf_public_severity --- cf_i18n_impact 0 cf_eta 无 cf_bug_source --- cf_viss 无 名称:0,长度:52
  • dataframe的图片(在ipython notebook中输入'df'即可):
Int64Index:924624 个条目,0 到 924623 数据列: bug_id 924624 非空值 assign_to 924624 个非空值 bug_file_loc 427318 非空值 bug_severity 924624 非空值 bug_status 924624 非空值 creation_ts 924624 个非空值 delta_ts 924624 个非空值 short_desc 924624 非空值 host_op_sys 924624 个非空值 guest_op_sys 924624 个非空值 优先级 924624 个非空值 rep_platform 924624 非空值 记者 924624 非空值 product_id 924624 非空值 category_id 924624 个非空值 component_id 924624 非空值 分辨率 924624 非空值 target_milestone 924624 个非空值 qa_contact 924624 非空值 status_whiteboard 924624 个非空值 投票 924624 个非空值 关键字 924624 个非空值 lastdiffed 924509 个非空值 everconfirmed 924624 个非空值 report_accessible 924624 非空值 cclist_accessible 924624 非空值 估计时间 924624 个非空值 剩余时间 924624 个非空值 截止日期 0 非空值 别名 0 非空值 found_in_product_id 924624 个非空值 found_in_version_id 924624 个非空值 found_in_phase_id 924624 个非空值 cf_type 924624 非空值 cf_reported_by 924624 个非空值 cf_attempted 89622 非空值 cf_failed 89587 非空值 cf_public_summary 510799 非空值 cf_doc_impact 924624 非空值 cf_security 924624 非空值 cf_build 327460 非空值 cf_branch 614929 非空值 cf_change 300612 非空值 cf_test_id 12610 非空值 cf_regression 924624 非空值 cf_reviewer 924624 非空值 cf_on_hold 924624 个非空值 cf_public_severity 924624 非空值 cf_i18n_impact 924624 非空值 cf_eta 3910 非空值 cf_bug_source 924624 非空值 cf_viss 725 个非空值 数据类型:float64(5)、int64(19)、object(28)
  • 在“convert_objects()”之后:
数据类型:datetime64[ns](2)、float64(5)、int64(19)、object(26)
  • 并将转换后的数据帧放入 hdfstore 成本:749.50 s :)
    • 似乎减少“对象”数据类型的数量是降低时间成本的关键
  • 并将转换后的数据帧放入 hdfstore 中,参数 'table' 为 true 仍会返回该错误
/usr/local/lib/python2.6/dist-packages/pandas-0.10.1.dev_6e2b6ea-py2.6-linux-x86_64.egg/pandas/io/pytables.pyc in create_axes(self, axes, obj, validate , nan_rep, data_columns, min_itemsize, **kwargs) 2203 加薪 2204 除外(例外),详细信息: -> 2205 raise Exception("找不到正确的原子类型 -> [dtype->%s] %s" % (b.dtype.name, str(detail))) 2206 j += 1 2207 例外:找不到正确的原子类型 -> [dtype->object] 类型“datetime.datetime”的对象没有 len()
  • 我正在尝试放置不带日期时间列的数据框

更新 4

  • mysql中有4列类型为datetime:
    • creation_ts
    • delta_ts
    • 最后的差异
    • 截止日期

调用convert_objects()后:

  • creation_ts:
时间戳:1998-05-06 21:27:00
  • delta_ts:
时间戳:2012-05-09 14:41:41
  • 最后的差异
datetime.datetime(2012, 5, 9, 14, 41, 41)
  • deadline 始终为 None,无论是在调用 'convert_objects' 之前还是之后
没有
  • 放置没有列 'lastdiff' 的数据框会花费 691.75 秒
  • 当放置没有列 'lastdiff' 的数据框并将参数 'table' 设置为 True 时,我遇到了一个新错误:
/usr/local/lib/python2.6/dist-packages/pandas-0.10.1.dev_6e2b6ea-py2.6-linux-x86_64.egg/pandas/io/pytables.pyc in create_axes(self, axes, obj, validate , nan_rep, data_columns, min_itemsize, **kwargs) 2203 加薪 2204 除外(例外),详细信息: -> 2205 raise Exception("找不到正确的原子类型 -> [dtype->%s] %s" % (b.dtype.name, str(detail))) 2206 j += 1 2207 例外:找不到正确的原子类型 -> 'Decimal' 类型的 [dtype->object] 对象没有 len()
  • mysql 中列 'estimated_time'、'remaining_time'、'cf_viss' 的类型是 'decimal'

更新 5

  • 我已通过以下代码将这些“十进制”类型列转换为“浮点”类型:
no_diffed_converted_df_bugs.estimated_time = no_diffed_converted_df_bugs.estimated_time.map(float)
  • 而现在,时间成本是 372.84 秒
  • 但是'table'版本的put仍然报错:
/usr/local/lib/python2.6/dist-packages/pandas-0.10.1.dev_6e2b6ea-py2.6-linux-x86_64.egg/pandas/io/pytables.pyc in create_axes(self, axes, obj, validate , nan_rep, data_columns, min_itemsize, **kwargs) 2203 加薪 2204 除外(例外),详细信息: -> 2205 raise Exception("找不到正确的原子类型 -> [dtype->%s] %s" % (b.dtype.name, str(detail))) 2206 j += 1 2207 例外:找不到正确的原子类型 -> [dtype->object] 类型为“datetime.date”的对象没有 len()

【问题讨论】:

  • 您能否提供您正在使用的代码以及 pandas 版本?也请张贴'ptdump -av file.h5';您打算只对索引或特定列使用表查询吗? (例如,给我您将执行的示例查询)
  • 请也发布操作系统..thxs
  • 猜测您有相当数量的字符串列;这在 0.10.0 上被破坏了...更新到 0.10.1-dev ....在 64 位 linux 上 100 个浮点列 + 20 个字符串列对我来说需要 20 秒,有 1M 条记录
  • 尝试我在下面给出的代码(并使用不同的文件);似乎您的 .h5 文件可能以某种方式损坏。你正在做的操作实际上会比我下面的要快得多,但根本无法查询 - 这就是你想要的吗?
  • 请更新到 0.10.1-dev

标签: python pandas


【解决方案1】:

如何加快速度?

  1. 使用“io.sql.read_frame”将数据从 sql db 加载到数据帧。因为“read_frame”会将类型为“十进制”的列转换为浮点数。
  2. 为每列填充缺失的数据。
  3. 在放置操作之前调用函数'DataFrame.convert_objects'
  4. 如果 dateframe 中有字符串类型的列,请使用“table”而不是“storer”

store.put('key', df, table=True)

做完这些工作后,同样数据集下put操作的性能有很大提升:

CPU times: user 42.07 s, sys: 28.17 s, total: 70.24 s
Wall time: 98.97 s

第二次测试的档案日志:

在 68.688 CPU 秒内进行 95984 次函数调用(95958 次原语调用) 排序:内部时间 ncalls tottime percall cumtime percall filename:lineno(function) 445 16.757 0.038 16.757 0.038 {numpy.core.multiarray.array} 19 16.250 0.855 16.250 0.855 {'tables.tableExtension.Table' 对象的方法'_append_records'} 16 7.958 0.497 7.958 0.497 {'numpy.ndarray'对象的方法'astype'} 19 6.533 0.344 6.533 0.344 {pandas.lib.create_hdf_rows_2d} 4 6.284 1.571 6.388 1.597 {'tables.tableExtension.Row'对象的方法'_fillCol'} 20 2.640 0.132 2.641 0.132 {pandas.lib.maybe_convert_objects} 1 1.785 1.785 1.785 1.785 {pandas.lib.isnullobj} 7 1.619 0.231 1.619 0.231 {'numpy.ndarray'对象的方法'flatten'} 11 1.059 0.096 1.059 0.096 {pandas.lib.infer_dtype} 1 0.997 0.997 41.952 41.952 pytables.py:2468(write_data) 19 0.985 0.052 40.590 2.136 pytables.py:2504(write_data_chunk) 1 0.827 0.827 60.617 60.617 pytables.py:2433(写入) 1504 0.592 0.000 0.592 0.000 {“tables.hdf5Extension.Array”对象的“_g_readSlice”方法} 4 0.534 0.133 13.676 3.419 pytables.py:1038(set_atom) 1 0.528 0.528 0.528 0.528 {pandas.lib.max_len_string_array} 4 0.441 0.110 0.571 0.143 internals.py:1409(_stack_arrays) 35 0.358 0.010 0.358 0.010 {方法'复制''numpy.ndarray'对象} 1 0.276 0.276 3.135 3.135 internals.py:208(fillna) 5 0.263 0.053 2.054 0.411 common.py:128(_isnull_ndarraylike) 48 0.253 0.005 0.253 0.005 {'tables.hdf5Extension.Array'对象的方法'_append'} 4 0.240 0.060 1.500 0.375 internals.py:1400(_simple_blockify) 1 0.234 0.234 12.145 12.145 pytables.py:1066(set_atom_string) 28 0.225 0.008 0.225 0.008 {'tables.hdf5Extension.Array' 对象的方法'_createCArray'} 36 0.218 0.006 0.218 0.006 {'tables.hdf5Extension.Array' 对象的方法'_g_writeSlice'} 6110 0.155 0.000 0.155 0.000 {numpy.core.multiarray.empty} 4 0.097 0.024 0.097 0.024 {方法'所有'的'numpy.ndarray'对象} 6 0.084 0.014 0.084 0.014 {tables.indexesExtension.keysort} 18 0.084 0.005 0.084 0.005 {'tables.hdf5Extension.Leaf'对象的方法'_g_close'} 11816 0.064 0.000 0.108 0.000 文件.py:1036(_getNode) 19 0.053 0.003 0.053 0.003 {'tables.hdf5Extension.Leaf'对象的方法'_g_flush'} 1528 0.045 0.000 0.098 0.000 array.py:342(_interpret_indexing) 11709 0.040 0.000 0.042 0.000 文件.py:248(__getitem__) 2 0.027 0.013 0.383 0.192 index.py:1099(get_neworder) 1 0.018 0.018 0.018 0.018 {numpy.core.multiarray.putmask} 4 0.013 0.003 0.017 0.004 index.py:607(final_idx32)

【讨论】:

    【解决方案2】:

    我非常确信您的问题与 DataFrames 中实际类型的类型映射以及 PyTables 如何存储它们有关。

    • 具有固定表示的简单类型(floats/ints/bools),它们被映射到固定的 c 类型
    • 如果可以正确转换日期时间,则会处理它们(例如,它们的 dtype 为 'datetime64[ns]',尤其是 datetimes.date 未处理(NaN 是另一回事,根据使用情况,可能会导致整个列类型处理不当)
    • 字符串被映射(在 Storer 对象中映射到 Object 类型,Table 将它们映射到 String 类型)
    • Unicode 未处理
    • 所有其他类型在 Storers 中作为 Object 处理,或者为 Tables 抛出异常

    这意味着如果你对一个存储器(一个固定的表示)进行 put,那么所有不可映射的类型都将变为 Object,请参阅此。 PyTables 腌制这些列。请参阅下面的 ObjectAtom 参考

    http://pytables.github.com/usersguide/libref/declarative_classes.html#the-atom-class-and-its-descendants

    表格将引发无效类型(我应该在此处提供更好的错误消息)。如果您尝试存储映射到 ObjectAtom 的类型(出于性能原因),我想我也会提供警告。

    要强制某些类型,请尝试以下一些:

    import pandas as pd
    
    # convert None to nan (its currently Object)
    # converts to float64 (or type of other objs)
    x = pd.Series([None])
    x = x.where(pd.notnull(x)).convert_objects()
    
    # convert datetime like with embeded nans to datetime64[ns]
    df['foo'] = pd.Series(df['foo'].values, dtype = 'M8[ns]')
    

    这是 64 位 linux 上的示例(文件为 1M 行,磁盘大小约为 1 GB)

    In [1]: import numpy as np
    
    In [2]: import pandas as pd
    
    In [3]: pd.__version__
    Out[3]: '0.10.1.dev'
    
    In [3]: import tables
    
    In [4]: tables.__version__
    Out[4]: '2.3.1'
    
    In [4]: df = pd.DataFrame(np.random.randn(1000 * 1000, 100), index=range(int(
       ...: 1000 * 1000)), columns=['E%03d' % i for i in xrange(100)])
    
    In [5]: for x in range(20):
       ...:     df['String%03d' % x] = 'string%03d' % x
    
    In [6]: df
    Out[6]: 
    <class 'pandas.core.frame.DataFrame'>
    Int64Index: 1000000 entries, 0 to 999999
    Columns: 120 entries, E000 to String019
    dtypes: float64(100), object(20)
    
    # storer put (cannot query) 
    In [9]: def test_put():
       ...:     store = pd.HDFStore('test_put.h5','w')
       ...:     store['df'] = df
       ...:     store.close()
    
    In [10]: %timeit test_put()
    1 loops, best of 3: 7.65 s per loop
    
    # table put (can query)
    In [7]: def test_put():
          ....:     store = pd.HDFStore('test_put.h5','w')
          ....:     store.put('df',df,table=True)
          ....:     store.close()
    
    
    In [8]: %timeit test_put()
    1 loops, best of 3: 21.4 s per loop
    

    【讨论】:

    • 谢谢杰夫。我想我明白了,现在我正在与大量的 None 值作斗争,'fillna' 方法非常有帮助。完成此操作后,我将在此处粘贴我的解决方案以及放置操作的速度。
    • 我添加了 1) 为我们拒绝的类型创建表时更好的错误消息,以及 2) 当您尝试放置将被腌制的列时的 PerformanceWarning。 - 您可以更新到我的存储库并尝试一下。 github.com/jreback/pandas/tree/pytables_update6
    • 也许解决 unicode 问题的一个不错的技巧/有点脏的方法是使用“xmlcharrefreplace”选项将 unicode 列转换为字符串列;稍后您可以根据需要将其翻译回 unicode。
    • PyTables 3.0.0 默认在 py3 中处理 unicode;您可以将 unicode 作为固定存储存储在 py2 中:唯一的限制是对表格(并且您总是可以进行某种混合 - 例如在表格中进行搜索然后加载 Unicode 并对其进行索引)
    猜你喜欢
    • 1970-01-01
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2014-12-23
    • 2019-09-03
    • 2019-10-17
    • 1970-01-01
    相关资源
    最近更新 更多