0、前言
Numpy的结构化数组其实就是ndarrays
,其数据类型由一系列命名字段的简单数据类型组成,如下:
name | age | wgt |
---|---|---|
Rex | 9 | 81.0 |
Fido | 3 | 27.0 |
>>> x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
... dtype=[('name', 'U10'), ('age', 'i4'), ('wgt', 'f4')])
>>> x
array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
dtype=[('name', 'S10'), ('age', '<i4'), ('wgt', '<f4')])
这里x
是长度为2的一维数组,每个元素为一个结构体(含三个元素的元组),我们可通过普通位置索引访问各个结构体,或者使用字段名访问所有结构体的指定字段。
>>> x[0]
('Rex', 9, 81.)
>>> x['name']
array(['Rex', 'Fido'], dtype='<U10')
>>> x[['name', 'age']]
array([('Rex', 9), ('Fido', 3)], dtype=[('name', '<U10'), ('age', '<i4')])
结构化数组的设计旨在模拟C语言中的’structs’,主要用于结构化数据的底层操作,如解释编译的二进制数据块。由于Numpy中的结构化数组类C结构内存布局会导致缓存性能不佳,对于表格数据的简单操作,可使用pandas、xarray
等其它高性能的接口。
1、结构化数据类型
结构化数据类型为固定长度的字节序列(itemsize),即一个字段集合。结构中的每个字段有对应的名称、数据类型和字节偏移量。数据类型可是任何Numpy数据类型,包括其它结构化类型或子数组。字节偏移用于优化存储,偏移量通常由Numpy自动确定。
结构化数据类型的创建
1. 元组列表,每个字段对应一个元组(fieldname,datatype,shape)
其中fieldname
为字段名字符串,datatype
为任何可转换为数据类型的对象,shape
为可选的指定子阵列形状的整数元组。此外,每个字段的字节偏移量和总体结构体中元素的大小由Numpy自动确定。
>>> np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2,2))])
dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
2. 逗号分隔的dtype规范
任何 string dtype specifications 都可以在字符串中使用逗号分隔,字段的总元素大小(itemsize)和字节偏移量由Numpy自动确定。
>>> 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))])
3. 字段参数数组的字典
names(必须) | formats(必须) | offsets(可选) | itemsize(可选) | aligned(可选) | titles(可选) |
---|---|---|---|---|---|
字段名列表 | 数据类型列表 | 字节偏移列表 | 所有字段总字节整数 | 是否自动偏移布尔值 | 标题列表 |
>>> a = np.dtype({'names':['name','age','wgt'], 'formats':['U10','i4','f4']})
>>> a
dtype([('name', '<U10'), ('age', '<i4'), ('wgt', '<f4')])
>>> a.fields
mappingproxy({'name': (dtype('<U10'), 0), 'age': (dtype('int32'), 40), 'wgt': (dtype('float32'), 44)})
>>> a.itemsize
48
>>> np.dtype({'names':['name','age','wgt'], 'formats':['U10','i4','f4'], 'offsets':[0,40,48]})
dtype({'names':['name','age','wgt'], 'formats':['<U10','<i4','<f4'], 'offsets':[0,40,48], 'itemsize':52})
其中’U’指Unicode字符串(1~4字节),占4个字节,故所有字段占用10*4+4+4=48个字节,已满足对齐要求,故itemsize=48。
4. 字段名称的字典
由于python较早版本的字典数据类型中键值对无序,不建议使用此种表示法。
自动字节偏移和对齐
通常,对齐结构可提高数据读写性能,但会占用更多的存储空间。 一般是在字段之间填充字节,使得每个字段的字节偏移量是该字段对齐的倍数,对于简单数据类型,通常等于字段字节的大小。
字段字节的偏移量和结构化数据类型的总字节大小,可自行设定或由Numpy自动确定。其中总占用字节(itemsize
)必须足够大,确保可以存储全部所有偏移后的字段。
>>> 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
>>> print_offsets(np.dtype('u1,u1,i4,u1,i8,u2', align=True))
offsets: [0, 1, 4, 8, 16, 24]
itemsize: 32
2、结构化数组的索引和分配
分配数据至结构化数组
1. 通过python原生元组赋值
>>> x = np.array([('Rex',9), ('Fido',3)], dtype=[('name','U10'), ('age','i4')])
>>> x
array([('Rex', 9), ('Fido', 3)], dtype=[('name', '<U10'), ('age', '<i4')])
>>> x = np.array([('Rex',9), ('Fido',3)], dtype='U10,i4')
>>> x
array([('Rex', 9), ('Fido', 3)], dtype=[('f0', '<U10'), ('f1', '<i4')])
2. 通过标量赋值
分配给结构化元素的标量将分配给所有字段(广播
):
>>> x = np.zeros(3,dtype='U2,i4,f4')
>>> x
array([('', 0, 0.), ('', 0, 0.), ('', 0, 0.)],
dtype=[('f0', '<U2'), ('f1', '<i4'), ('f2', '<f4')])
>>> x[:] = 3333
>>> x
array([('33', 3333, 3333.), ('33', 3333, 3333.), ('33', 3333, 3333.)],
dtype=[('f0', '<U2'), ('f1', '<i4'), ('f2', '<f4')])
>>> x[:] = range(3)
>>> x
array([('0', 0, 0.), ('1', 1, 1.), ('2', 2, 2.)],
dtype=[('f0', '<U2'), ('f1', '<i4'), ('f2', '<f4')])
3. 来自其他结构化数组的赋值
两个结构化数组(字段数相同
)之间的分配,就像源元素已转换为元组然后分配给目标元素一样。 也就是说,源阵列的第一个字段分配给目标数组的第一个字段,第二个字段同样分配,依此类推。
>>> a = np.zeros(2, dtype='f4,i4')
>>> a
array([(0., 0), (0., 0)], dtype=[('f0', '<f4'), ('f1', '<i4')])
>>> b = np.ones(2, dtype='i4,S3')
>>> b
array([(1, b'1'), (1, b'1')], dtype=[('f0', '<i4'), ('f1', 'S3')])
>>> b[:]=a
>>> b
array([(0, b'0'), (0, b'0')], dtype=[('f0', '<i4'), ('f1', 'S3')])
索引结构化数组
1. 访问单个字段
可以通过使用字段名称索引数组来访问和修改结构化数组的各个字段,返回视图。
>>> x = np.array([(1,2),(3,4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
>>> x['foo']
array([1, 3], dtype=int64)
>>> x['foo'] = 10
>>> x
array([(10, 2.), (10, 4.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
>>> y = x['bar']
>>> y[:] = 10
>>> x
array([(10, 10.), (10, 10.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
2. 访问多个字段
可以索引并分配具有多字段索引的结构化数组,其中索引是字段名称列表。
警告: 多字段索引的说明将从Numpy 1.15升级Numpy 1.16。 |
在Numpy 1.15中,多字段索引的结果是原始数组的副本,如下所示:
>>> a = np.zeros(3, dtype=[('a', 'i4'), ('b', 'i4'), ('c', 'f4')])
>>> a
array([(0, 0, 0.), (0, 0, 0.), (0, 0, 0.)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<f4')])
>>> b = a[['a','c']]
>>> b
array([(0, 0.), (0, 0.), (0, 0.)], dtype=[('a', '<i4'), ('c', '<f4')])
>>> b[:] = 10
>>> b
array([(10, 10.), (10, 10.), (10, 10.)],
dtype=[('a', '<i4'), ('c', '<f4')])
>>> a
array([(0, 0, 0.), (0, 0, 0.), (0, 0, 0.)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<f4')])
3. 用整数索引获取结构化标量
索引结构化数组的单个元素(带有整数索引)返回结构化标量,结构化标量是可变的,并且像原始数组的视图一样,因此可通过修改标量来修改原始数组,此外结构化变量还支持按字段名进行访问和赋值。
>>> x = np.array([(1,2),(3,4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
>>> x
array([(1, 2.), (3, 4.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
>>> s = x[0]
>>> s
(1, 2.)
>>> s['bar'] = 100
>>> s[0] = 1000
>>> x
array([(1000, 100.), ( 3, 4.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
结构化数组相等性比较
两个结构化数组相等性比较的结果,如下:
- 若两个待比较的结构化数组的dtype相等(字段名称、数据类型等均相等),则比较结果将生成一个具有原始数组维度的布尔数组;
- 若两个待比较的结构化数组的dtype中除字段名之外均相等,则比较结果将返回一个布尔值,True或False;
- 若两个待比较的结构化数组的dtype中出现字段长度或相同位置的数据类型等不一致,均溢出异常;
>>> a = np.zeros(2, dtype=[('a', 'i4'), ('b', 'f4')])
>>> b = np.ones(2, dtype=[('a', 'i4'), ('b', 'f4')])
>>> b > a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'numpy.ndarray' and 'numpy.ndarray'
>>> b == a
array([False, False])
>>> b = np.ones(2, dtype=[('a1', 'i4'), ('b1', 'f4')])
>>> a == b
False