使用 field-name 索引时,结构化数组会删除 field-titles

Structured array drops field-titles when being indexed with field-name

numpy (v1.13.1) 的以下行为是错误还是设计使然?

>>>import numpy as np
>>>a = np.zeros((1,), dtype=[(('title 1', 'x'), '|i1'), (('title 2', 'y'), '>f4')])

>>>a.dtype.descr
[(('title 1', 'x'), '|i1'), (('title 2', 'y'), '>f4')]

>>>a[['y','x']].dtype.descr
[('y', '>f4'), ('x', '|i1')]

# I would have expected the previous expression to have returned this instead: 
# [(('title 2', 'y'), '>f4'), (('title 1', 'x'), '|i1')]

请注意,当使用 field-names 索引数组时,字段的字段 标题 是如何消失的。 (我的意思是字段-标题,而不是字段-名称!)

这个问题有解决办法吗?


附录:为什么标题是一个很棒的功能

由于似乎很多人认为标题应该(甚至已经)被弃用,我将尝试宣传为什么它们是 dtypes 的一个重要特性:

我获得了包含数十个字段的大型 data-series,并且发现为了代码的可读性,简洁 field-names 非常有用。标题使我能够以人类可读的形式额外拥有现场文档,而不会牺牲名称的简洁性。

特别方便的是,名称和标题都被分配到数据最初产生的地方,并通过管道传递:采集-->处理-->存储... 沿着这条管道丢失标题特别不方便,因为它们往往越往下越有用,例如已序列化用于存储或交换的 numpy 数组:只需这两行代码即可将 any ndarray 持久化到数据库或通过电线:

serialized_array.dtype = repr(numpy_array.dtype.descr)
serialized_array.buffer = base64.b64encode(numpy_array.tostring())

并且该数据的任何 consumer/receiver 都具有可读纯文本形式的完整文档化数据类型的优势,其中标题记录了字段的重要方面,例如物理单位(例如:毫米或英寸?)

重新创建一个如上所示序列化的 numpy 数组也是用两行代码完成的,每个字段的文档也包括在内:

bytes_buffer = base64decode(serialized_array.buffer)
numpy_array = np.frombuffer(bytes_buffer, dtype=eval(serialized_array.dtype))

另请注意,标题的存在实际上允许使用更简洁的 field-names,例如而不是

('PosX_mm', '>f4') 

你可以

(('Position along X-axis [mm]', 'x'), '>f4')

后者看起来声明起来很麻烦,但考虑一下用法,例如在 pyplot 中:

plt.plot(a['x'])
plt.ylabel(a.dtype.fields['x'][2])

请注意,这不仅非常可读,而且还降低了不得不重构所有出现的 field-name 'x' 的风险,只是因为缺少 documentation/titles 迫使你更改field-name以避免误解。标题字符串可以在数据起源的地方修改,更改只会通过管道传播,而无需进一步更改 source-code.

所以你的 dtype 是:

In [68]: dt3=np.dtype([(('title 1', 'x'), '|i1'), (('title 2', 'y'), '>f4')])
In [69]: dt3
Out[69]: dtype([(('title 1', 'x'), 'i1'), (('title 2', 'y'), '>f4')])
In [70]: dt3.descr
Out[70]: [(('title 1', 'x'), '|i1'), (('title 2', 'y'), '>f4')]
In [71]: dt3.fields
Out[71]: 
mappingproxy({'title 1': (dtype('int8'), 0, 'title 1'),
              'title 2': (dtype('>f4'), 1, 'title 2'),
              'x': (dtype('int8'), 0, 'title 1'),
              'y': (dtype('>f4'), 1, 'title 2')})
In [72]: dt3.names
Out[72]: ('x', 'y')

在文档中,titles通常以这种格式提供:

In [79]: np.dtype({'names':['x','y'],
                   'formats':['|i1','>f4'],
                   'titles':['title 1','title 2']})
Out[79]: dtype([(('title 1', 'x'), 'i1'), (('title 2', 'y'), '>f4')])

标题是罕见的特征,因此更有可能包含粗糙的边缘,不符合用户期望的东西。

那么标题有什么用呢?一种可能性是他们提供了一个替代字段访问密钥:

In [82]: a['x']
Out[82]: array([0], dtype=int8)
In [83]: a['title 1']
Out[83]: array([0], dtype=int8)

请注意,在 fields 字典中,有标题条目和姓名条目。

使用字段名或标题进行索引 returns 具有更简单数据类型的数组,在本例中为 native dtype:

In [86]: a['title 1'].dtype
Out[86]: dtype('int8')
In [87]: a['title 1'].dtype.descr
Out[87]: [('', '|i1')]
In [88]: a['title 1'].dtype.fields
In [94]: a['title 1'].dtype.isnative
Out[94]: True
In [96]: a['title 1'].dtype.type
Out[96]: numpy.int8
In [97]: dt3.type
Out[97]: numpy.void

复合数据类型也可以嵌套,例如

In [98]: np.dtype([('foo',dt3)])
Out[98]: dtype([('foo', [(('title 1', 'x'), 'i1'), (('title 2', 'y'), '>f4')])])

并用 'foo' 索引它会产生一个 dt3 dtype。

在任何情况下,使用字段名称或标题进行索引都会降低嵌套级别,并且不会保留偏移量或标题。这些是 parent dtype dt3 的属性,而不是 sub-dtype.

的属性

用列表建立索引会创建一个新的 dtype,有一个名称,但没有标题:

In [102]: a[['x']]
Out[102]: 
array([(0,)],
      dtype=[('x', 'i1')])
In [103]: a[['x','title 1']]
Out[103]: 
array([(0, 0)],
      dtype=[('x', 'i1'), ('title 1', 'i1')])

我可能没有添加任何新信息,但至少对我来说,标题的作用得到了澄清。

如果您需要新视图中的标题,则必须明确提供。例如:

In [113]: dt3x = np.dtype([(('title 1', 'x'), '|i1')])
In [114]: a['x'].view(dt3x)
Out[114]: 
array([(0,)],
      dtype=[(('title 1', 'x'), 'i1')])
In [115]: _['title 1']
Out[115]: array([0], dtype=int8)

说到嵌套数据类型,我们可以保留 'title 1' 如果它是嵌套的

In [117]: dt4=np.dtype([('x', np.dtype([('title 1','|i1')])), ('y', np.dtype([('
     ...: title 2', '>f4')]))])
In [118]: dt4
Out[118]: dtype([('x', [('title 1', 'i1')]), ('y', [('title 2', '>f4')])])
In [119]: b = np.zeros((2,), dt4)
In [120]: b
Out[120]: 
array([((0,), ( 0.,)), ((0,), ( 0.,))],
      dtype=[('x', [('title 1', 'i1')]), ('y', [('title 2', '>f4')])])
In [121]: b['x']
Out[121]: 
array([(0,), (0,)],
      dtype=[('title 1', 'i1')])
In [122]: b['x']['title 1']
Out[122]: array([0, 0], dtype=int8)

我曾将其报告为 issue and Allan Haldane has fixed