>>> x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
... dtype=[('name', 'U10'), ('age', 'i4'), ('weight', 'f4')])
array([('Rex', 9, 81.), ('Fido', 3, 27.)],
dtype=[('name', 'U10'), ('age', '<i4'), ('weight', '<f4')])
这x
是一个长度为2的一维数组,其数据类型是具有三个字段的结构:1.长度为10或更短的字符串,名称为“ name”; 2。一个32位整数,名称为“ age”; 3。一个32位浮点数,称为“ weight”。
如果x
在位置1 索引,您将得到一个结构:
>>> x[1]
('Fido', 3, 27.0)
您可以通过使用字段名称建立索引来访问和修改结构化数组的各个字段:
>>> x['age']
array([9, 3], dtype=int32)
>>> x['age'] = 5
array([('Rex', 5, 81.), ('Fido', 5, 27.)],
dtype=[('name', 'U10'), ('age', '<i4'), ('weight', '<f4')])
结构化数据类型旨在模仿C语言中的“结构”,并共享相似的内存布局。它们用于与C代码进行接口并用于结构化缓冲区的低级操作,例如用于解释二进制Blob。为此,它们支持特殊功能,例如子数组,嵌套数据类型和联合,并允许控制结构的内存布局。
希望处理诸如存储在csv文件中的表格数据的用户可能会发现其他更合适的pydata项目,例如xarray,pandas或DataArray。这些为表格数据分析提供了高级界面,并针对该用途进行了更好的优化。例如,与之相比,numpy中结构化数组的类似于C结构的内存布局可能导致较差的缓存行为。
结构化数据类型
可以将结构化数据类型视为具有一定长度的字节序列(结构的itemssize),该字节序列被解释为字段的集合。每个字段在结构中都有一个名称,一个数据类型和一个字节偏移量。字段的数据类型可以是任何numpy数据类型,包括其他结构化数据类型,也可以是子数组数据类型,其行为类似于指定形状的ndarray。字段的偏移是任意的,并且字段甚至可能重叠。这些偏移量通常由numpy自动确定,但也可以指定。
结构化数据类型创建
可以使用函数创建结构化数据类型numpy.dtype
。规范有4种替代形式,其灵活性和简洁性各不相同。这些在“ 数据类型对象”参考页面中有进一步的记录
,总结如下:
元组列表,每个字段一个元组
每个元组的形状都是可选的。是一个字符串(如果使用标题则为元组,请参见
下面的字段标题),可以是可转换为数据类型的任何对象,并且是指定子数组形状的整数元组。(fieldname, datatype, shape)
fieldname
datatype
shape
>>> np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])
dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
如果fieldname
为空字符串''
,则将为该字段提供默认的形式名称f#
,其中#
是该字段的整数索引,从左侧的0开始计数:
>>> np.dtype([('x', 'f4'), ('', 'i4'), ('z', 'i8')])
dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])
结构内字段的字节偏移量和总结构项大小会自动确定。
一串用逗号分隔的dtype规范
在这种简写形式中,可以在字符串中使用任何字符串dtype规范,并用逗号分隔。字段的itemsize和字节偏移量自动确定和字段名被给予默认名称f0
,
f1
等等。
>>> np.dtype('i8, f4, S3')
dtype([('f0', '<i8'), ('f1', '<f4'), ('f2', 'S3')])
>>> np.dtype('3int8, float32, (2, 3)float64')
dtype([('f0', 'i1', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])
字段参数数组字典
这是规范的最灵活形式,因为它允许控制字段的字节偏移量和结构的项目大小。
该词典有两个必需的键:“名称”和“格式”,以及四个可选键,“偏移”,“ itemsize”,“ aligned”和“ titles”。“名称”和“格式”的值应分别为字段名称列表和dtype规范列表,且长度相同。可选的“偏移量”值应该是整数字节偏移量的列表,该偏移量对应于结构中的每个字段。如果未给出“偏移”,则自动确定偏移。可选的“ itemsize”值应为整数,以dtype为单位描述字节的总大小,该大小必须足够大以包含所有字段。
>>> np.dtype({'names': ['col1', 'col2'], 'formats': ['i4', 'f4']})
dtype([('col1', '<i4'), ('col2', '<f4')])
>>> np.dtype({'names': ['col1', 'col2'],
... 'formats': ['i4', 'f4'],
... 'offsets': [0, 4],
... 'itemsize': 12})
dtype({'names':['col1','col2'], 'formats':['<i4','<f4'], 'offsets':[0,4], 'itemsize':12})
可以选择偏移量以使字段重叠,尽管这将意味着分配给一个字段可能会破坏任何重叠字段的数据。作为例外,numpy.object
类型的字段不能与其他字段重叠,因为存在破坏内部对象指针然后对其取消引用的风险。
可以将可选的'aligned'值设置为True
使自动偏移量计算使用对齐的偏移量(请参阅自动字节偏移量和Alignment),就像'align'关键字参数numpy.dtype
已设置为True一样。
可选的“标题”值应为与“名称”长度相同的标题列表,请参见下面的字段标题。
字段名称字典
不鼓励使用这种形式的规范,但请在此处进行记录,因为较早的numpy代码可能会使用它。字典的键是字段名称,值是指定类型和偏移量的元组:
>>> np.dtype({'col1': ('i1', 0), 'col2': ('f4', 1)})
dtype([('col1', 'i1'), ('col2', '<f4')])
不建议使用此格式,因为Python字典不会在Python 3.6之前的Python版本中保留顺序,并且结构化dtype中字段的顺序具有含义。字段标题可以使用三元组指定,请参见下文。
可以names
在dtype对象的属性中找到结构化数据类型的字段名称的列表:
>>> d = np.dtype([('x', 'i8'), ('y', 'f4')])
>>> d.names
('x', 'y')
可以通过names
使用相同长度的字符串序列分配给属性来修改字段名称。
dtype对象还具有类似于字典的属性,fields
其关键字是字段名称(和字段标题,请参见下文),其值是包含每个字段的dtype和字节偏移量的元组。
>>> d.fields
mappingproxy({'x': (dtype('int64'), 0), 'y': (dtype('float32'), 8)})
对于非结构化数组,names
和fields
属性都将相等None
。推荐的测试dtype是否结构化的方法是使用dt.names不是None而不是if dt.names来解决具有0字段的dtype。
如果可能的话,以“元组列表”形式显示结构化数据类型的字符串表示形式,否则numpy会转而使用更通用的字典形式。
自动字节偏移和对齐
Numpy使用两种方法之一来自动确定字段字节偏移量和结构化数据类型的整体项目大小,具体取决于是否将
align=True
其指定为的关键字参数numpy.dtype
。
默认情况下(align=False
),numpy会将字段打包在一起,以便每个字段都从上一个字段结束的字节偏移处开始,并且这些字段在内存中是连续的。
>>> def print_offsets(d):
... print("offsets:", [d.fields[name][1] for name in d.names])
... print("itemsize:", d.itemsize)
>>> print_offsets(np.dtype('u1, u1, i4, u1, i8, u2'))
offsets: [0, 1, 2, 6, 7, 15]
itemsize: 17
如果align=True
设置为,numpy将以许多C编译器填充C结构的方式填充结构。在某些情况下,对齐的结构可以提高性能,但要增加数据类型的大小。将填充字节插入字段之间,以便每个字段的字节偏移量将是该字段对齐方式的倍数,对于简单数据类型,通常等于该字段的大小(以字节为单位),请参见PyArray_Descr.alignment
。该结构还将添加尾随填充,以便其项大小是最大字段对齐方式的倍数。
>>> print_offsets(np.dtype('u1, u1, i4, u1, i8, u2', align=True))
offsets: [0, 1, 4, 8, 16, 24]
itemsize: 32
请注意,尽管默认情况下几乎所有现代C编译器都采用这种方式进行填充,但C结构中的填充是C实现相关的,因此,不能保证此内存布局与C程序中相应结构的布局完全匹配。可能需要在numpy或C方面进行一些工作,以获得确切的对应关系。
如果offsets
在基于字典的dtype规范中使用可选键指定了偏移量,则setting align=True
将检查每个字段的偏移量是否是其大小的倍数,而itemsize是最大字段大小的倍数,如果不是,则引发异常。
如果结构化数组的字段偏移量和项目大小满足对齐条件,则该数组将具有ALIGNED
flag
集合。
便利函数numpy.lib.recfunctions.repack_fields
将对齐的dtype或数组转换为压缩的dtype或数组,反之亦然。它使用dtype或结构化ndarray作为参数,并返回带有字段重新包装的副本(带有或不带有填充字节)。
字段标题
除字段名称外,字段还可以具有关联的标题(备用名称),有时将其用作该字段的附加描述或别名。标题可以像字段名一样用于索引数组。
为了在使用dtype规范的元组列表形式时添加标题,可以将字段名指定为两个字符串的元组,而不是单个字符串,这将分别是字段的标题和字段名。例如:
>>> np.dtype([(('my title', 'name'), 'f4')])
dtype([(('my title', 'name'), '<f4')])
当使用基于字典的规范的第一形式时,标题可以'titles'
如上所述被提供为额外的关键字。当使用第二个(折损的)基于字典的规范时,可以通过提供3元素元组而不是通常的2元素元组来提供标题:(datatype, offset, title)
>>> np.dtype({'name': ('i4', 0, 'my title')})
dtype([(('my title', 'name'), '<i4')])
该dtype.fields
字典将包含标题作为键,如果使用任何头衔。这实际上意味着具有标题的字段将在字段字典中两次表示。这些字段的元组值还将具有第三个元素,即字段标题。因此,由于该names
属性保留字段顺序,而该fields
属性可能不保留,因此建议使用dtype names
属性(不列出标题)遍历dtype的字段,如下所示:
>>> for name in d.names:
... print(d.fields[name][:2])
(dtype('int64'), 0)
(dtype('float32'), 8)
结构化数据类型numpy.void
默认在numpy中实现为具有基本类型
,但是可以使用Data Type Objects中描述的dtype规范的形式
将其他numpy类型解释为结构化类型。这里是所需的基础dtype,将从中复制字段和标志
。此dtype类似于C中的“联合”。(base_dtype, dtype)
base_dtype
dtype
索引和分配给结构化数组
将数据分配给结构化数组
有多种方法可以将值分配给结构化数组:使用python元组,使用标量值或使用其他结构化数组。
从Python本机类型(Tuples)分配
将值分配给结构化数组的最简单方法是使用python元组。每个分配的值都应是一个长度等于数组中字段数的元组,而不是列表或数组,因为它们将触发numpy的广播规则。元组的元素从左到右分配给数组的连续字段:
>>> x = np.array([(1, 2, 3), (4, 5, 6)], dtype='i8, f4, f8')
>>> x[1] = (7, 8, 9)
array([(1, 2., 3.), (7, 8., 9.)],
dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '<f8')])
标量分配
分配给结构化元素的标量将分配给所有字段。当将标量分配给结构化数组,或将非结构化数组分配给结构化数组时,会发生这种情况:
>>> x = np.zeros(2, dtype='i8, f4, ?, S1')
>>> x[:] = 3
array([(3, 3., True, b'3'), (3, 3., True, b'3')],
dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
>>> x[:] = np.arange(2)
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 scalar from dtype([('A', '<i4'), ('B', '<i4')]) to dtype('int32') according to the rule 'unsafe'
从其他结构化数组分配
发生两个结构化数组之间的分配,就像源元素已转换为元组然后分配给目标元素一样。也就是说,无论字段名称如何,源数组的第一个字段都被分配给目标数组的第一个字段,第二个字段也是如此,依此类推。字段数不同的结构化数组不能互相分配。未包含在任何字段中的目标结构的字节不受影响。
>>> a = np.zeros(3, dtype=[('a', 'i8'), ('b', 'f4'), ('c', 'S3')])
>>> b = np.ones(3, dtype=[('x', 'f4'), ('y', 'S3'), ('z', 'O')])
>>> b[:] = a
array([(0., b'0.0', b''), (0., b'0.0', b''), (0., b'0.0', b'')],
dtype=[('x', '<f4'), ('y', 'S3'), ('z', 'O')])
访问各个字段
通过使用字段名称索引数组,可以访问和修改结构化数组的各个字段。
>>> x = np.array([(1, 2), (3, 4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
>>> x['foo']
array([1, 3])
>>> x['foo'] = 10
array([(10, 2.), (10, 4.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
生成的数组是原始数组的视图。它共享相同的内存位置,写入视图将修改原始数组。
>>> y = x['bar']
>>> y[:] = 11
array([(10, 11.), (10, 11.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
该视图具有与索引字段相同的dtype和项大小,因此,除了嵌套结构外,它通常是非结构化数组。
>>> 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)
使用多字段索引建立索引的结果是原始数组的视图,如下所示:
>>> 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起提出,类似的代码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)
array([(2, 0, 3.), (2, 0, 3.), (2, 0, 3.)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<f4')])
这符合上述结构化数组分配规则。例如,这意味着可以使用适当的多字段索引交换两个字段的值:
>>> a[['a', 'c']] = a[['c', 'a']]
用整数索引以获得结构化标量
索引结构化数组的单个元素(具有整数索引)将返回结构化标量:
>>> 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
array([(1, 100.), (3, 4.)],
dtype=[('foo', '<i8'), ('bar', '<f4')])
与元组相似,结构化标量也可以用整数索引:
>>> scalar = np.array([(1, 2., 3.)], dtype='i, f, f')[0]
>>> scalar[0]
>>> scalar[1] = 4
因此,元组可能被认为是等同于numpy的结构化类型的本机Python,就像本机python整数等同于numpy的整数类型一样。可通过调用ndarray.item
以下方法将结构化标量转换为元组:
>>> scalar.item(), type(scalar.item())
((1, 4.0, 3.0), <class 'tuple'>)
结构比较
如果两个void结构化数组的dtype相等,则测试数组的相等性将导致布尔数组具有原始数组的尺寸,并且将元素设置为True
对应结构的所有字段均相等的位置。如果字段名称,dtype和标题相同(忽略字节序)且字段顺序相同,则结构化dtype相等。
>>> a = np.zeros(2, dtype=[('a', 'i4'), ('b', 'i4')])
>>> b = np.ones(2, dtype=[('a', 'i4'), ('b', 'i4')])
>>> a == b
array([False, False])
当前,如果两个void结构化数组的dtype不相等,则比较失败,返回标量值False
。从numpy 1.10开始不推荐使用此行为,并且将来会引发错误或执行逐元素比较。
在<
与>
运营商总是返回False
比较空洞结构阵列时,与算术和位操作不被支持。
记录阵列
作为可选的便利,numpy numpy.recarray
在numpy.rec
子模块中提供了ndarray子类,以及关联的辅助函数,该
允许按属性而不是仅按索引访问结构化数组的字段。记录数组还使用一种特殊的数据类型,numpy.record
该类型允许按属性访问从该数组获得的结构化标量上的字段。
创建记录数组的最简单方法是numpy.rec.array
:
>>> recordarr = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")],
... dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')])
>>> recordarr.bar
array([ 2., 3.], dtype=float32)
>>> recordarr[1:2]
rec.array([(2, 3., b'World')],
dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])
>>> recordarr[1:2].foo
array([2], dtype=int32)
>>> recordarr.foo[1:2]
array([2], dtype=int32)
>>> recordarr[1].baz
b'World'
numpy.rec.array
可以将各种参数转换为记录数组,包括结构化数组:
>>> arr = np.array([(1, 2., 'Hello'), (2, 3., "World")],
... dtype=[('foo', 'i4'), ('bar', 'f4'), ('baz', 'S10')])
>>> recordarr = np.rec.array(arr)
该numpy.rec
模块为创建记录数组提供了许多其他便利功能,请参见Numpy 中文网