添加和访问 numpy 结构化数组的对象类型字段
Add and access object-type field of a numpy structured array
我正在使用 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
类型的另一个字段 "test" 添加到数组 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, though the issue is old and I do not know whether the behaviour I am observing is actually a bug. 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
?
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')]
大小和数据类型正确的新数组:
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
In order to prevent clobbering object pointers in fields of numpy.object type, numpy currently does not allow views of structured arrays containing objects.
这一行显然是指使用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.
使用对象数据类型创建复合数据类型没有问题:
In [186]: np.array([None,None,None], dtype=[('test',object)])
Out[186]: array([(None,), (None,), (None,)], dtype=[('test', 'O')])
但是我没有看到任何能够连接 a
和 data
的 recfunctions
。
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 数组开始,然后尝试 view
和 i8
(相同大小的 dtype),我得到了同样的错误。因此,对对象数据类型 view
的限制并不限于结构化数组。在对象指针指向 i8
的情况下需要这样的限制是有道理的。在将对象指针嵌入复合数据类型的情况下,对这种限制的需求可能并不那么引人注目。它甚至可能是矫枉过正,或者只是简单安全地使用它的情况。
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 行中的测试检查新旧数据类型的 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 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
类型的另一个字段 "test" 添加到数组 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, though the issue is old and I do not know whether the behaviour I am observing is actually a bug. 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
?
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')]
大小和数据类型正确的新数组:
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
In order to prevent clobbering object pointers in fields of numpy.object type, numpy currently does not allow views of structured arrays containing objects.
这一行显然是指使用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.
使用对象数据类型创建复合数据类型没有问题:
In [186]: np.array([None,None,None], dtype=[('test',object)])
Out[186]: array([(None,), (None,), (None,)], dtype=[('test', 'O')])
但是我没有看到任何能够连接 a
和 data
的 recfunctions
。
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 数组开始,然后尝试 view
和 i8
(相同大小的 dtype),我得到了同样的错误。因此,对对象数据类型 view
的限制并不限于结构化数组。在对象指针指向 i8
的情况下需要这样的限制是有道理的。在将对象指针嵌入复合数据类型的情况下,对这种限制的需求可能并不那么引人注目。它甚至可能是矫枉过正,或者只是简单安全地使用它的情况。
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 行中的测试检查新旧数据类型的 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']]))