【问题标题】:Add and access object-type field of a numpy structured array添加和访问 numpy 结构化数组的对象类型字段
【发布时间】:2019-04-17 23:32:46
【问题描述】:

我正在使用 numpy 1.16.2。

简而言之,我想知道如何将对象类型字段添加到结构化数组中。通过recfunctions 模块的标准方式会引发错误,我想这是有原因的.因此,我想知道我的解决方法是否有任何问题。此外,我想了解为什么需要这种解决方法,以及在访问新创建的数组时是否需要格外小心。

下面是详细信息:

我有一个 numpy 结构化数组:

import numpy as np
a = np.zeros(3, dtype={'names':['A','B','C'], 'formats':['int','int','float']})
for i in range(len(a)):
    a[i] = i

我想将object 类型的另一个字段“测试”添加到数组a。执行此操作的标准方法是使用 numpy 的 recfunctions 模块:

import numpy.lib.recfunctions as rf
b = rf.append_fields(a, "test", [None]*len(a)) 

此代码抛出错误:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-4a7be4f94686> in <module>
----> 1 rf.append_fields(a, "test", [None]*len(a))

D:\_Programme\Anaconda3\lib\site-packages\numpy\lib\recfunctions.py in append_fields(base, names, data, dtypes, fill_value, usemask, asrecarray)
    718     if dtypes is None:
    719         data = [np.array(a, copy=False, subok=True) for a in data]
--> 720         data = [a.view([(name, a.dtype)]) for (name, a) in zip(names, data)]
    721     else:
    722         if not isinstance(dtypes, (tuple, list)):

D:\_Programme\Anaconda3\lib\site-packages\numpy\lib\recfunctions.py in <listcomp>(.0)
    718     if dtypes is None:
    719         data = [np.array(a, copy=False, subok=True) for a in data]
--> 720         data = [a.view([(name, a.dtype)]) for (name, a) in zip(names, data)]
    721     else:
    722         if not isinstance(dtypes, (tuple, list)):

D:\_Programme\Anaconda3\lib\site-packages\numpy\core\_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

here 讨论了一个类似的错误,虽然这个问题很老,我不知道我观察到的行为是否实际上是一个错误。 Here我被告知不支持包含一般对象的结构化数组的视图。

因此我建立了一个解决方法:

b = np.empty(len(a), dtype=a.dtype.descr+[("test", object)])
b[list(a.dtype.names)] = a

这行得通。尽管如此,我有以下问题:

问题

  • 为什么需要这种解决方法?这只是一个错误吗?
  • 使用新数组b 似乎与使用a 没有什么不同。变量c = b[["A", "test"]] 显然是b 数据的视图。那么为什么would they say 不支持数组b 上的视图呢?我是否必须格外小心对待c

【问题讨论】:

  • 我不认为recfunctions标准。它们是可以使用的实用程序,但您不必使用它们。它们没有被编译,因此不会做任何没有它们就不能做的事情。
  • stackoverflow.com/questions/42364725/… - 基本上是相同的问题,尝试将 append_fields 与对象 dtype 一起使用。另一种方法是附加一个datetime64 字段。

标签: python numpy structured-array


【解决方案1】:
In [161]: a = np.zeros(3, dtype={'names':['A','B','C'], 'formats':['int','int','
     ...: float']}) 
     ...: for i in range(len(a)): 
     ...:     a[i] = i 
     ...:                                                                       
In [162]: a                                                                     
Out[162]: 
array([(0, 0, 0.), (1, 1, 1.), (2, 2, 2.)],
      dtype=[('A', '<i8'), ('B', '<i8'), ('C', '<f8')])

定义新的数据类型:

In [164]: a.dtype.descr                                                         
Out[164]: [('A', '<i8'), ('B', '<i8'), ('C', '<f8')]
In [165]: a.dtype.descr+[('test','O')]                                          
Out[165]: [('A', '<i8'), ('B', '<i8'), ('C', '<f8'), ('test', 'O')]
In [166]: dt= a.dtype.descr+[('test','O')]                                      

正确大小和 dtype 的新数组:

In [167]: b = np.empty(a.shape, dt)                                             

按字段名称将值从a 复制到b

In [168]: for name in a.dtype.names: 
     ...:     b[name] = a[name] 
     ...:                                                                       
In [169]: b                                                                     
Out[169]: 
array([(0, 0, 0., None), (1, 1, 1., None), (2, 2, 2., None)],
      dtype=[('A', '<i8'), ('B', '<i8'), ('C', '<f8'), ('test', 'O')])

许多rf 函数逐字段复制:

rf.recursive_fill_fields(a,b)

rf.append_fields 在初始化 output 数组后使用它。

在早期版本中,多字段索引会生成副本,因此 b[list(a.dtype.names)] = a 之类的表达式将不起作用。


我不知道是否值得尝试弄清楚 rf.append_fields 在做什么。这些功能有些陈旧,并且没有大量使用(注意特殊导入)。因此,它们完全有可能存在不起作用的错误或边缘情况。我检查过的函数的功能与我演示的一样 - 创建一个新的 dtype,结果数组,并按字段名称复制数据。

在最近的版本中,访问多个字段的方式发生了变化。 recfunctions 中有一些新功能可以方便使用结构化数组,例如 repack_fields

https://docs.scipy.org/doc/numpy/user/basics.rec.html#accessing-multiple-fields

我不知道这是否适用于append_fields 问题。我看到还有一个关于带对象的结构化数组的部分,但我没有研究过:

https://docs.scipy.org/doc/numpy/user/basics.rec.html#viewing-structured-arrays-containing-objects

为了防止破坏 numpy.object 类型字段中的对象指针,numpy 目前不允许查看包含对象的结构化数组。

这行显然是指使用view 方法。由字段索引创建的视图,无论是单名称还是多字段列表,都不受影响。


append_fields中的错误来自这个操作:

In [183]: data = np.array([None,None,None])                                          
In [184]: data                                                                       
Out[184]: array([None, None, None], dtype=object)
In [185]: data.view([('test',object)])                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-185-c46c4464b53c> in <module>
----> 1 data.view([('test',object)])

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

使用对象 dtypes 创建复合 dtype 没有问题:

In [186]: np.array([None,None,None], dtype=[('test',object)])                        
Out[186]: array([(None,), (None,), (None,)], dtype=[('test', 'O')])

但我没有看到任何能够加入adatarecfunctions


view可用于更改a的字段名:

In [219]: a.view([('AA',int),('BB',int),('cc',float)])                               
Out[219]: 
array([(0, 0, 0.), (1, 1, 1.), (2, 2, 2.)],
      dtype=[('AA', '<i8'), ('BB', '<i8'), ('cc', '<f8')])

但尝试为b 这样做会因为同样的原因而失败:

In [220]: b.view([('AA',int),('BB',int),('cc',float),('d',object)])                  
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-220-ab0a6e4dd57f> in <module>
----> 1 b.view([('AA',int),('BB',int),('cc',float),('d',object)])

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

我从一个对象 dtype 数组开始,并尝试使用 viewi8(相同大小的 dtype),我得到同样的错误。因此,对对象 dtype 的view 的限制不仅限于结构化数组。在对象指针指向i8 的情况下需要这样的限制是有道理的。在将对象指针嵌入复合 dtype 的情况下,对这种限制的需求可能并不那么引人注目。这甚至可能是矫枉过正,或者只是简单地玩安全和简单的情况。

In [267]: x.dtype                                                                    
Out[267]: dtype('O')
In [268]: x.shape                                                                    
Out[268]: (3,)
In [269]: x.dtype.itemsize                                                           
Out[269]: 8
In [270]: x.view('i8')                                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-270-30c78b13cd10> in <module>
----> 1 x.view('i8')

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

请注意,第 493 行中的测试检查了新旧 dtype 的 hasobject 属性。一个更细微的测试可能会检查两者是否hasobject,但我怀疑逻辑可能会变得相当复杂。有时,简单的禁止比一组复杂的测试更安全(也更容易)。


在进一步测试中

In [283]: rf.structured_to_unstructured(a)                                           
Out[283]: 
array([[ 3.,  3.,  0.],
       [12., 10.,  1.],
       [ 2.,  2.,  2.]])

但尝试在b 上执行相同操作,甚至其字段的子集都会产生熟悉的错误:

rf.structured_to_unstructured(b)
rf.structured_to_unstructured(b[['A','B','C']]) 

我必须先使用repack 来制作无对象副本:

rf.structured_to_unstructured(rf.repack_fields(b[['A','B','C']])) 

【讨论】:

  • 感谢您的回答。您能否指出您的解决方案与我最初的解决方案有何不同?最近对 numpy 的更改正是我必须问这个问题的原因。您介意解决我提出的其他问题吗?到目前为止,对我来说唯一的新信息是 recfunctions 是旧的和非标准的。
  • 您的解决方案和我的解决方案可能没有任何真正的区别。我只是喜欢自己思考问题。
  • 感谢您更新您的答案。我知道错误出现在哪里;我把错误信息放在我的问题中。 (我真的很感谢你的努力,但你真的读过这个问题吗?)结果数组的行为是否与标准数组不同(由于提到的“视图”问题)?
  • 您在寻找更深入的答案吗? b.view(b.dtype) 似乎是唯一有效的 .view 表达式。使用a.view(...) 我可以更改字段名称。我不能用b 做到这一点。它引发了相同的_internal 错误。由b 的字段索引创建的views 是另一回事。
  • 您对b[list(a.dtype.names)] = a 的使用取决于最新版本,其中多字段索引生成视图。以前的版本制作了副本。我迭代了在早期代码中工作的字段名称。见rf.recursive_fill_fields(a,b)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多