Numpy结构化数组的创建、索引与分配

0、前言

Numpy的结构化数组其实就是ndarrays,其数据类型由一系列命名字段的简单数据类型组成,如下:

nameagewgt
Rex981.0
Fido327.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中除字段名之外均相等,则比较结果将返回一个布尔值,TrueFalse
  • 若两个待比较的结构化数组的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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值