NumPy 结构化数组的索引和赋值
1、将数据分配给结构化数组
有多种方法可以为结构化数组赋值:使用 python 元组、使用标量值或使用其他结构化数组。
1.1 从 Python 本机类型(元组)赋值
为结构化数组赋值的最简单方法是使用 python 元组。每个分配的值应该是一个长度等于数组中字段数的元组,而不是列表或数组,因为它们会触发 numpy 的广播规则。元组的元素被分配给数组的连续字段,从左到右:
>>> x = np.array([(1, 2, 3), (4, 5, 6)], dtype='i8, f4, f8')
>>> x[1] = (7, 8, 9)
>>> x
array([(1, 2., 3.), (7, 8., 9.)],
dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '<f8')])
1.2 从标量赋值
分配给结构化元素的标量将分配给所有字段。当将标量分配给结构化数组或将非结构化数组分配给结构化数组时,就会发生这种情况:
>>> x = np.zeros(2, dtype='i8, f4, ?, S1')
>>> x[:] = 3
>>> x
array([(3, 3., True, b'3'), (3, 3., True, b'3')],
dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
>>> x[:] = np.arange(2)
>>> x
array([(0, 0., False, b'0'), (1, 1., True, b'1')],
dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
结构化数组也可以分配给非结构化数组,但前提是结构化数据类型只有一个字段:
>>> twofield = np.zeros(2, dtype=[('A', 'i4'), ('B', 'i4')])
>>> onefield = np.zeros(2, dtype=[('A', 'i4')])
>>> nostruct = np.zeros(2, dtype='i4')
>>> nostruct[:] = twofield
Traceback (most recent call last):
...
TypeError: Cannot cast array data from dtype([('A', '<i4'), ('B', '<i4')]) to dtype('int32') according to the rule 'unsafe'
1.3 从其他结构化数组赋值
两个结构化数组之间的赋值就像将源元素转换为元组然后赋值给目标元素一样。也就是说,源数组的第一个字段被分配给目标数组的第一个字段,第二个字段同样如此,以此类推,无论字段名称如何。具有不同字段数的结构化数组不能相互分配。未包含在任何字段中的目标结构字节不受影响。
>>> a = np.zeros(3, dtype=[('a', 'i8'), ('b', 'f4'), ('c', 'S3')])
>>> b = np.ones(3, dtype=[('x', 'f4'), ('y', 'S3'), ('z', 'O')])
>>> b[:] = a
>>> b
array([(0., b'0.0', b''), (0., b'0.0', b''), (0., b'0.0', b'')],
dtype=[('x', '<f4'), ('y', 'S3'), ('z', 'O')])
1.4 涉及子数组赋值
当分配给作为子数组的字段时,分配的值将首先广播到子数组的形状。
2、索引结构化数组
2.1访问单个字段
可以通过使用字段名称索引数组来访问和修改结构化数组的各个字段。
>>> x = np.array([(1, 2), (3, 4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
>>> x['foo']
array([1, 3])
>>> x['foo'] = 10
>>> x
array([(10, 2.), (10, 4.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
结果数组是原始数组的视图。它共享相同的内存位置,写入视图将修改原始数组。
>>> y = x['bar']
>>> y[:] = 11
>>> x
array([(10, 11.), (10, 11.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
此视图与索引字段具有相同的 dtype 和 itemsize,因此它通常是非结构化数组,嵌套结构除外。
>>> y.dtype, y.shape, y.strides
(dtype('float32'), (2,), (12,))
如果访问的字段是子数组,则子数组的维度将附加到结果的形状中:
>>> x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
>>> x['a'].shape
(2, 2)
>>> x['b'].shape
(2, 2, 3, 3)
2.2 访问多个字段
可以索引并分配给具有多字段索引的结构化数组,其中索引是字段名称列表。
警告
多字段索引的行为从 Numpy 1.15 更改为 Numpy 1.16。
使用多字段索引进行索引的结果是原始数组的视图,如下所示:
>>> a = np.zeros(3, dtype=[('a', 'i4'), ('b', 'i4'), ('c', 'f4')])
>>> a[['a', 'c']]
array([(0, 0.), (0, 0.), (0, 0.)],
dtype={'names':['a','c'], 'formats':['<i4','<f4'], 'offsets':[0,8], 'itemsize':12})
分配给视图会修改原始数组。视图的字段将按照它们被索引的顺序排列。请注意,与单字段索引不同,视图的 dtype 与原始数组具有相同的项大小,并且具有与原始数组中相同偏移量的字段,并且仅缺少未索引的字段。
警告
在 Numpy 1.15 中,使用多字段索引对数组进行索引会返回上述结果的副本,但字段在内存中打包在一起,就像通过numpy.lib.recfunctions.repack_fields
.
与 1.15 相比,Numpy 1.16 的新行为导致在未索引字段的位置有额外的“填充”字节。您将需要更新任何依赖于具有“打包”布局的数据的代码。例如代码,例如:
&& a[['a', 'c']].view('i8') # Fails in Numpy 1.16
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: When changing to a smaller dtype, its size must be a divisor of the size of original dtype
将需要改变。此代码FutureWarning
自 Numpy 1.12起引发了 a ,FutureWarning
自 1.7起也引发了类似的代码。
在 1.16 中,numpy.lib.recfunctions
模块中引入了许多功能 以帮助用户解决此更改。这些是numpy.lib.recfunctions.repack_fields
。numpy.lib.recfunctions.structured_to_unstructured
,numpy.lib.recfunctions.unstructured_to_structured
,numpy.lib.recfunctions.apply_along_fields
,numpy.lib.recfunctions.assign_fields_by_name
和numpy.lib.recfunctions.require_fields
。
该函数numpy.lib.recfunctions.repack_fields
始终可用于重现旧行为,因为它将返回结构化数组的打包副本。例如,上面的代码可以替换为:
&& from numpy.lib.recfunctions import repack_fields
&& repack_fields(a[['a', 'c']]).view('i8') # supported in 1.16
array([0, 0, 0])
此外,numpy 现在提供了一个新函数numpy.lib.recfunctions.structured_to_unstructured
,对于希望将结构化数组转换为非结构化数组的用户来说,这是一种更安全、更有效的替代方法,正如上面的视图通常所做的那样。与视图不同,此函数允许在考虑填充的情况下安全转换为非结构化类型,通常避免复制,并根据需要转换数据类型。代码如:
&& b = np.zeros(3, dtype=[('x', 'f4'), ('y', 'f4'), ('z', 'f4')])
&& b[['x', 'z']].view('f4')
array([0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)
可以通过替换为更安全:
&& from numpy.lib.recfunctions import structured_to_unstructured
&& structured_to_unstructured(b[['x', 'z']])
array([0, 0, 0])
分配给具有多字段索引的数组会修改原始数组:
>>> a[['a', 'c']] = (2, 3)
>>> a
array([(2, 0, 3.), (2, 0, 3.), (2, 0, 3.)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<f4')])
这遵守上述结构化数组分配规则。例如,这意味着可以使用适当的多字段索引交换两个字段的值:
>>> a[['a', 'c']] = a[['c', 'a']]
2.3 用整数索引以获得结构化标量
索引结构化数组的单个元素(使用整数索引)返回结构化标量:
>>> x = np.array([(1, 2., 3.)], dtype='i, f, f')
>>> scalar = x[0]
>>> scalar
(1, 2., 3.)
>>> type(scalar)
<class 'numpy.void'>
与其他 numpy 标量不同,结构化标量是可变的,就像原始数组的视图一样,因此修改标量将修改原始数组。结构化标量还支持按字段名称访问和分配:
>>> x = np.array([(1, 2), (3, 4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
>>> s = x[0]
>>> s['bar'] = 100
>>> x
array([(1, 100.), (3, 4.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
与元组类似,结构化标量也可以用整数索引:
>>> scalar = np.array([(1, 2., 3.)], dtype='i, f, f')[0]
>>> scalar[0]
1
>>> scalar[1] = 4
因此,元组可能被认为是原生 Python 等价于 numpy 的结构化类型,就像原生 Python 整数等同于 numpy 的整数类型一样。可以通过调用将结构化标量转换为元组numpy.ndarray.item
:
>>> scalar.item(), type(scalar.item())
((1, 4.0, 3.0), <class 'tuple'>)
3、查看包含对象的结构化数组
为了防止破坏object
类型字段中的对象指针 ,numpy 目前不允许包含对象的结构化数组的视图。
4、结构比较
如果两个 void 结构化数组的 dtypes 相等,则测试数组的相等性将产生一个具有原始数组维度的布尔数组,元素设置为True
相应结构的所有字段相等的位置。如果字段名称、dtypes 和标题相同,忽略字节顺序,并且字段的顺序相同,则结构化 dtypes 是相等的:
>>> a = np.zeros(2, dtype=[('a', 'i4'), ('b', 'i4')])
>>> b = np.ones(2, dtype=[('a', 'i4'), ('b', 'i4')])
>>> a == b
array([False, False])
目前,如果两个 void 结构化数组的 dtypes 不相等,则比较失败,返回标量值False
。此行为自 numpy 1.10 起已弃用,将来会引发错误或执行元素比较。
在<
与>
运营商总是返回False
比较空洞结构阵列时,与算术和位操作不被支持。
更多建议: