将两个 NumPy 数组组合成一个结构化数组以附加到 PyTables table

Combine two NumPy arrays into one structured array for appending to a PyTables table

我有两个非结构化 NumPy 数组 ab,形状分别为 (N,)(N, 256, 2),数据类型为 np.float。我希望将它们组合成一个结构化数组,形状为 (N,) 和 dtype [('field1', np.float), ('field2', np.float, (256, 2))].

这方面的文档出奇地缺乏。我找到了 np.lib.recfunctions.merge_arrays 之类的方法,但未能找到执行此操作所需的精确功能组合。


为了避免the XY problem,我将陈述我的更广泛的目标。

我有一个布局为 {"field1": tables.FloatCol(), "field2": tables.FloatCol(shape = (256, 2))} 的 PyTables table。两个 NumPy 数组表示要附加到每个字段的 N 个新行。 N 很大,所以我希望通过一次高效的 table.append(rows) 调用来完成此操作,而不是通过 table.row['field'] = ....

的缓慢循环过程

The table.append documentation

The rows argument may be any object which can be converted to a structured array compliant with the table structure (otherwise, a ValueError is raised). This includes NumPy structured arrays, lists of tuples or array records, and a string or Python buffer.

将我的数组转换为适当的结构化数组似乎是我应该在这里做的事情。我正在寻找速度,我预计其他选项会更慢。

定义数据类型,并创建一个empty/zeros数组:

In [163]: dt = np.dtype([('field1', np.float), ('field2', np.float, (4, 2))])            
In [164]: arr = np.zeros(3, dt)     # float display is prettier                                                          
In [165]: arr                                                                            
Out[165]: 
array([(0., [[0., 0.], [0., 0.], [0., 0.], [0., 0.]]),
       (0., [[0., 0.], [0., 0.], [0., 0.], [0., 0.]]),
       (0., [[0., 0.], [0., 0.], [0., 0.], [0., 0.]])],
      dtype=[('field1', '<f8'), ('field2', '<f8', (4, 2))])

按字段分配值:

In [166]: arr['field1'] = np.arange(3)                                                   
In [167]: arr['field2'].shape                                                            
Out[167]: (3, 4, 2)
In [168]: arr['field2'] = np.arange(24).reshape(3,4,2)                                   
In [169]: arr                                                                            
Out[169]: 
array([(0., [[ 0.,  1.], [ 2.,  3.], [ 4.,  5.], [ 6.,  7.]]),
       (1., [[ 8.,  9.], [10., 11.], [12., 13.], [14., 15.]]),
       (2., [[16., 17.], [18., 19.], [20., 21.], [22., 23.]])],
      dtype=[('field1', '<f8'), ('field2', '<f8', (4, 2))])

np.rec 确实有一个功能类似:

In [174]: np.rec.fromarrays([np.arange(3.), np.arange(24).reshape(3,4,2)], dtype=dt)     
Out[174]: 
rec.array([(0., [[ 0.,  1.], [ 2.,  3.], [ 4.,  5.], [ 6.,  7.]]),
           (1., [[ 8.,  9.], [10., 11.], [12., 13.], [14., 15.]]),
           (2., [[16., 17.], [18., 19.], [20., 21.], [22., 23.]])],
          dtype=[('field1', '<f8'), ('field2', '<f8', (4, 2))])

这是一样的,除了字段可以作为属性访问(也)。在幕后,它执行相同的按字段分配。

numpy.lib.recfunctions 是结构化数组函数的另一个集合。这些也大多遵循按字段分配方法。

为了获得大小合适的测试打印输出,我的解决方案假定:

  • N = 5,
  • 第二维度 - 只有 4(而不是你的 256)。

要生成结果,请执行以下操作:

  1. import numpy.lib.recfunctions as rfn 开始(很快就会需要)。

  2. 创建源数组:

    a = np.array([10, 20, 30, 40, 50])
    b = np.arange(1, 41).reshape(5, 4, 2)
    
  3. 创建结果:

    result = rfn.unstructured_to_structured(
        np.hstack((a[:,np.newaxis], b.reshape(-1,8))),
        np.dtype([('field1', 'f4'), ('field2', 'f4', (4,2))]))
    

生成的数组包含:

array([(10., [[ 1.,  2.], [ 3.,  4.], [ 5.,  6.], [ 7.,  8.]]),
       (20., [[ 9., 10.], [11., 12.], [13., 14.], [15., 16.]]),
       (30., [[17., 18.], [19., 20.], [21., 22.], [23., 24.]]),
       (40., [[25., 26.], [27., 28.], [29., 30.], [31., 32.]]),
       (50., [[33., 34.], [35., 36.], [37., 38.], [39., 40.]])],
      dtype=[('field1', '<f4'), ('field2', '<f4', (4, 2))])

注意unstructured_to_structured的源数组被创建 以下方式:

  • 0 - 来自 a(转换为列),
  • 剩余的列 - 来自 b 的重塑方式使得所有元素 各自的 4 * 2 切片被转换为单行。数据 从每一行(从这些列)转换回“4 * 2” 通过这个函数塑造。
  • 以上两个组件都是用hstack组装的。

在上面的实验中,我假定类型为 f4,也许你应该更改 f8(您的决定)。

在代码的目标版本中:

  • field2第一维的4改为256,
  • b.reshape中的8改为512(=2*256 ).

这个答案建立在@hpualj 的答案之上。他的第一个方法将 obj 参数创建为结构化数组,第二个方法创建一个记录数组。 (当您追加时,该数组将是 rows 参数。)当我的数据已经在结构化(或记录)数组中时,我喜欢这两种方法来创建或追加到 tables。但是,如果您的数据位于单独的数组中(如“避免 X-Y 问题”中所述),则不必执行此操作。如 PyTables 文档中所述 table.append():

The rows argument may be any object which can be converted to a structured array compliant with the table structure.... This includes NumPy structured arrays, lists of tuples or array records...

换句话说,您可以附加引用数组的列表,只要它们与示例中使用 description=dt 创建的 table 结构相匹配即可。 (我认为您在创建时仅限于结构化数组。)这可能会简化您的代码。

我写了一个基于@hpaulj 代码的示例。它使用不同的方法创建 2 个相同的 HDF5 文件。

  • 对于第一个文件 (_1.h5),我使用结构化数组方法创建 table。然后我使用 table.append([list of arrays])
  • 将 3 行数据添加到 table
  • 对于第二个文件 (_2.h5),我创建 table 引用 使用 description=dt 的结构化数组 dtype,但不要使用 obj=arr 添加数据。然后我使用 table.append([list of arrays]) 将前 3 行数据添加到 table 并重复添加 3 行。

示例如下:

import numpy as np
import tables as tb

dt = np.dtype([('field1', np.float), ('field2', np.float, (4, 2))])            
arr = np.zeros(3, dt)     # float display is prettier                                                          
arr['field1'] = np.arange(3)                                                                                                           
arr['field2'] = np.arange(24).reshape(3,4,2)                                   

with tb.File('SO_62104084_1.h5','w') as h5f1:
    test_tb = h5f1.create_table('/','test',obj=arr)
    arr1 = np.arange(13.,16.,1.)                                                                                                           
    arr2 = np.arange(124.,148.,1.).reshape(3,4,2)          
# add rows of data referencing list of arrays: 
    test_tb.append([arr1,arr2])

with tb.File('SO_62104084_2.h5','w') as h5f2:
    test_tb=h5f2.create_table('/','test', description=dt)
    # add data rows 0-2:  
    arr1 = np.arange(3)                                                                                                           
    arr2 = np.arange(24).reshape(3,4,2)                                   
    test_tb.append([arr1,arr2])
# add data rows 3-5:   
    arr1 = np.arange(13.,16.,1.)                                                                                                           
    arr2 = np.arange(124.,148.,1.).reshape(3,4,2)          
    test_tb.append([arr1,arr2])