使用 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)
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)