【问题标题】:Is there a simple way to remove "padding" fields from numpy.dtype.descr?有没有一种简单的方法可以从 numpy.dtype.descr 中删除“填充”字段?
【发布时间】:2021-10-13 15:21:53
【问题描述】:

上下文

numpy 1.16 版开始,如果您访问结构化数组的多个字段,则结果数组的dtype 将具有与原始数组相同的项大小,从而导致额外的“填充”:

与 1.15 相比,Numpy 1.16 的新行为导致在未索引字段的位置有额外的“填充”字节。您将需要更新任何依赖于具有“打包”布局的数据的代码。

这可能会导致问题,例如如果您想稍后将字段添加到相关数组中:

import numpy as np
import numpy.lib.recfunctions


a = np.array(
    [
        (10.0, 13.5, 1248, -2),
        (20.0, 0.0, 0, 0),
        (30.0, 0.0, 0, 0),
        (40.0, 0.0, 0, 0),
        (50.0, 0.0, 0, 999)
    ], dtype=[('x', '<f8'), ('y', '<f8'), ('i', '<i8'), ('j', '<i8')]
)  # some array stolen from here: https://stackoverflow.com/a/37081693/5472354
print(a.shape, a.dtype, a.dtype.names, a.dtype.descr)
# all good so far

b = a[['x', 'i']]  # for further processing I only need certain fields
print(b.shape, b.dtype, b.dtype.names, b.dtype.descr)
# you will only notice the extra padding in the descr

# b = np.lib.recfunctions.repack_fields(b)
# workaround

# now when I add fields, this becomes an issue
c = np.empty(b.shape, dtype=b.dtype.descr + [('c', 'i4')])
c[list(b.dtype.names)] = b
c['c'] = 1

print(c.dtype.names)
print(c['f1'])
# the void fields are filled with raw data and were given proper names
# that can be accessed

现在一种解决方法是使用numpy.lib.recfunctions.repack_fields,它会删除填充,我将来会使用它,但对于我之前的代码,我需要修复。 (虽然可以有issuesrecfunctions,因为可能找不到模块;对我来说就是这种情况,因此附加了import numpy.lib.recfunctions 语句。)

问题

这部分代码是我用来给数组添加字段的(基于this):

c = np.empty(b.shape, dtype=b.dtype.descr + [('c', 'i4')])
c[list(b.dtype.names)] = b
c['c'] = 1

虽然(现在我知道了)使用numpy.lib.recfunctions.require_fields 可能更适合添加字段。但是,我仍然需要一种方法来删除 b.dtype.descr 中的空字段:

[('x', '<f8'), ('', '|V8'), ('i', '<i8'), ('', '|V8')]

这只是tupleslist,所以我想我可以构建一个或多或少尴尬的方式(沿着descr.remove(('', '|V8')))来处理这个问题,但我想知道是否有更好的方式,特别是因为空隙的大小取决于遗漏字段的数量,例如如果连续有两个,则从 V8 到 V16,依此类推(而不是每个遗漏字段都有一个新的 void)。所以代码会很快变得非常笨重。

【问题讨论】:

  • 这个padding 不是免费的。具有字段子集的真正 view 仍然必须访问原始数据,因此它必须“知道”原始 dtype 布局 repack 使用自己的数据副本创建一个新数组。导入 recfunctions 是正常的 - 请参阅我最近的 SO 答案,stackoverflow.com/questions/69546473/…
  • 在制作新数组时,许多recfunctions 会编辑dtype、组合字段、选择字段等。因此使用descr 中的元组是正常的。跨度>
  • 除了repackstructured_to_unstructured 等最近添加了1.16 更改需求的功能外,大部分recfunctions 已经存在很长时间了。 recarray 可能继承自 numpy 的前身,尽管它在很大程度上被当前的 dtypestructured array 版本所取代。
  • 您的第一条评论很有道理。虽然如果访问多个字段会创建一个view,这是否意味着建议的便利功能here 已经过时了?
  • 你能详细说明为什么单独导入recfunctions是正常的吗?我想我不明白。

标签: python numpy structured-array


【解决方案1】:
In [237]: a = np.array(
     ...:     [
     ...:         (10.0, 13.5, 1248, -2),
     ...:         (20.0, 0.0, 0, 0),
     ...:         (30.0, 0.0, 0, 0),
     ...:         (40.0, 0.0, 0, 0),
     ...:         (50.0, 0.0, 0, 999)
     ...:     ], dtype=[('x', '<f8'), ('y', '<f8'), ('i', '<i8'), ('j', '<i8')]
     ...:     )
In [238]: a
Out[238]: 
array([(10., 13.5, 1248,  -2), (20.,  0. ,    0,   0),
       (30.,  0. ,    0,   0), (40.,  0. ,    0,   0),
       (50.,  0. ,    0, 999)],
      dtype=[('x', '<f8'), ('y', '<f8'), ('i', '<i8'), ('j', '<i8')])

b 视图:

In [240]: b = a[['x','i']]
In [241]: b
Out[241]: 
array([(10., 1248), (20.,    0), (30.,    0), (40.,    0), (50.,    0)],
      dtype={'names':['x','i'], 'formats':['<f8','<i8'], 'offsets':[0,16], 'itemsize':32})

重新打包的副本:

In [243]: c = rf.repack_fields(b)
In [244]: c
Out[244]: 
array([(10., 1248), (20.,    0), (30.,    0), (40.,    0), (50.,    0)],
      dtype=[('x', '<f8'), ('i', '<i8')])
In [245]: c.dtype
Out[245]: dtype([('x', '<f8'), ('i', '<i8')])

您在添加字段时过度填充的尝试:

In [247]: d = np.empty(b.shape, dtype=b.dtype.descr + [('c', 'i4')])
     ...: d[list(b.dtype.names)] = b
     ...: d['c'] = 1
In [248]: d
Out[248]: 
array([(10., b'\x00\x00\x00\x00\x00\x00\x00\x00', 1248, b'\x00\x00\x00\x00\x00\x00\x00\x00', 1),
       (20., b'\x00\x00\x00\x00\x00\x00\x00\x00',    0, b'\x00\x00\x00\x00\x00\x00\x00\x00', 1),
       ...],
      dtype=[('x', '<f8'), ('f1', 'V8'), ('i', '<i8'), ('f3', 'V8'), ('c', '<i4')])

我第一次尝试制作不包含 Void 字段的 dtype。我不知道简单地测试 V 是否足够强大:

In [253]: [des for des in b.dtype.descr if not 'V' in des[1]]
Out[253]: [('x', '<f8'), ('i', '<i8')]

并从中创建一个新的 dtype:

In [254]: d_dtype = _ + [('c','i4')]

所有这些都是正常的 python 列表和元组操作。我在其他recfunctions 看到过。我怀疑repack_fields 做了这样的事情。

现在我们用更简单的 dtype 创建一个新数组:

In [255]: d = np.empty(b.shape, dtype=d_dtype)
In [256]: d[list(b.dtype.names)] = b
     ...: d['c'] = 1
In [257]: d
Out[257]: 
array([(10., 1248, 1), (20.,    0, 1), (30.,    0, 1), (40.,    0, 1),
       (50.,    0, 1)], dtype=[('x', '<f8'), ('i', '<i8'), ('c', '<i4')])

我从 repack_fields 中提取了构造一个新的、未填充的 dtype 的代码:

In [262]: def foo(a):
     ...:     fieldinfo = []
     ...:     for name in a.names:
     ...:         tup = a.fields[name]
     ...:         fmt = tup[0]
     ...:         if len(tup) == 3:
     ...:             name = (tup[2], name)
     ...:         fieldinfo.append((name, fmt))
     ...:     print(fieldinfo)
     ...:     dt = np.dtype(fieldinfo)
     ...:     return dt
     ...: 
     ...: 
In [263]: foo(b.dtype)
[('x', dtype('float64')), ('i', dtype('int64'))]
Out[263]: dtype([('x', '<f8'), ('i', '<i8')])

这适用于dtype.fields,而不是dtype.descr。一个是dict,另一个是列表。

In [274]: b.dtype
Out[274]: dtype({'names':['x','i'], 'formats':['<f8','<i8'], 'offsets':[0,16], 'itemsize':32})
In [275]: b.dtype.descr
Out[275]: [('x', '<f8'), ('', '|V8'), ('i', '<i8'), ('', '|V8')]
In [276]: b.dtype.fields
Out[276]: mappingproxy({'x': (dtype('float64'), 0), 'i': (dtype('int64'), 16)})
In [277]: b.dtype.fields['x']
Out[277]: (dtype('float64'), 0)

b.dtype 获取有效descr 元组的另一种方法:

In [278]: [des for des in b.dtype.descr if des[0] in b.dtype.names]
Out[278]: [('x', '<f8'), ('i', '<i8')]

【讨论】:

  • 感谢您提供的所有信息!我真的很喜欢你的最后一种方法。这很容易理解似乎很健壮。
猜你喜欢
  • 2010-09-13
  • 2011-06-03
  • 2014-12-08
  • 2023-04-03
  • 2010-12-05
  • 1970-01-01
  • 2010-12-12
  • 2011-02-17
相关资源
最近更新 更多