跳转至

8.11.结构化数组

import numpy as np
import pprint
Windows 10
Python 3.7.3 @ MSC v.1915 64 bit (AMD64)
Latest build date 2020.03.28
numpy version:  1.18.1

结构化数组常用操作

x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
             dtype=[('name', 'U10'), 
                    ('age', 'i4'), 
                    ('weight', 'f4')])

pprint.pprint(x)
array([('Rex', 9, 81.), ('Fido', 3, 27.)],
      dtype=[('name', '<U10'), ('age', '<i4'), ('weight', '<f4')])

通过索引,可以获得一个结构

pprint.pprint(x[0])

# 可以通过使用 字段名称 建立索引来访问和修改结构化数组的各个 字段:
pprint.pprint(x["name"])
pprint.pprint(x[["age","name"]])
# 根据 字段名称 做过滤
pprint.pprint(x[x["age"]>5]["name"])
# 修改全部数组的age字段
x["age"] = 20
pprint.pprint(x)
('Rex', 9, 81.)
array(['Rex', 'Fido'], dtype='<U10')
array([(9, 'Rex'), (3, 'Fido')],
      dtype={'names':['age','name'], 'formats':['<i4','<U10'],
'offsets':[40,0], 'itemsize':48})
array(['Rex'], dtype='<U10')
array([('Rex', 20, 81.), ('Fido', 20, 27.)],
      dtype=[('name', '<U10'), ('age', '<i4'), ('weight', '<f4')])

创建结构化数组

list of tuple

可以通过 a list of tuple 的形式创建结构化数组。

Tuple的形式是(fieldname, datatype, Shape)fieldname是字符串,如果使用title,则为元组。fieldnameShape可以省略,如果fieldname省略,则用 f# 的默认名称代替,shape 如果省略,则默认为(1,)

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')])

逗号分割的字符串

可以通过“A string of comma-separated dtype specifications”创建结构化数组。字符串要符合规范。

np.dtype('i8, f4, S3')

np.dtype('3int8, float32, (2, 3)float64')
dtype([('f0', 'i1', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])

dict of field parameter arrays

通过“关键字参数组成的字典”创建结构化数组。这是最灵活的规范形式,因为它允许控制字段的字节偏移和结构的项目大小。

dict有两个必需键namesformat,以及四个可选键 offsetsitemsizeAlignedtitle

  • namesformat的值应该分别是相同长度的字段名列表和dtype规范列表。

  • 可选的 offsets 值应该是整数字节偏移量的列表,结构中的每个字段都有一个偏移量。如果未给出 offsets ,则自动确定偏移量。

  • 可选的 itemsize 值应该是一个整数, 描述dtype的总大小(以字节为单位),它必须足够大以包含所有字段。

  • 可选的Aligned值可以设置为True,以使自动偏移计算使用对齐的偏移量(请参阅自动字节偏移量和对齐 )。

  • 可选的titles值应该是长度与names相同的标题列表。

np.dtype({'names': ['col1', 'col2'], 'formats': ['i4', '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})

dict of fieldnam

通过 dict of fieldname 创建结构化数组。不鼓励使用这种形式,因为Python字典在Python 3.6之前的Python版本中不保留顺序,但结构化dtype中字段的顺序有意义。

字典的关键字是字段名称,值是指定类型和偏移量的元组:

np.dtype({'col1': ('i1', 0), 'col2': ('f4', 1)})
dtype([('col1', 'i1'), ('col2', '<f4')])

操作和显示结构化数据类型

d = np.dtype([('x', 'i8'), ('y', 'f4')])

print(d.names)
print(d.fields)
('x', 'y')
{'x': (dtype('int64'), 0), 'y': (dtype('float32'), 8)}

为什么需要结构化数组

numpy结构化数组中的用法好像类似于Python的dict of list,那为什么还要用numpy结构化数组呢?

因为numpy的结构化数组底层是类似C语言的结构,占用一块连续的内存区域,并且numpy底层是C实现,numpy数组中的类型都是静态类型的,性能好于Python的的字典列表。

可以来做一下性能比较。

import time

### 定义计时器
def timer(func):
   def wrapper(self, *args, **kwargs):
       start = time.process_time()
       something = func(self, *args, **kwargs)
       end = time.process_time()
       type_str = func.__repr__()

       print("当前函数:", type_str, "\n", "用时:",end - start, "秒", sep="")

       return something
   return wrapper

# numpy版本长一岁
@timer
def addage_numpy(data, loop):
    for i in range(loop):
        data['age'] += 1

# python循环长一岁
@timer
def addage_python(data, loop):
    for j in range(loop):
        for i in range(4):
            data[i]["age"] += 1

# 创建结构化数组
names = ['Lin', 'Pan', 'Shen', 'Zhou']
ages = [28, 33, 34, 29] 
grades = [25, 26, 27, 24] 
data_np = np.zeros(4, dtype={
    'names':('name', 'age', 'grade'),
    'formats':('U10', 'i4', 'i4')
})
data_np['name'] = names
data_np['age'] = ages
data_np['grade'] = grades


data_py = []
for i in range(4):
    person = {"name": names[i], "age": ages[i], "grade": grades[i]}
    data_py.append(person)


addage_numpy(data_np, loop=1000000)
addage_python(data_py, loop=1000000)
当前函数:<function addage_numpy at 0x0000021944ECBA60>
用时:4.453125秒
当前函数:<function addage_python at 0x0000021944ECB9D8>
用时:1.421875秒

记录数组

记录数组和结构数组没有太大区别,它们所使用的场景也是类似的,只不过记录数组可以通过属性的方式访问数据。

创建记录数组的最简单方法是numpy.rec.array

recordarr = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")],                  
                         dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')])
print(type(recordarr))
# 通过属性访问数据
recordarr.foo
<class 'numpy.recarray'>
array([1, 2])

numpy.rec.array 可以将各种参数转换为记录数组,包括结构化数组:

arr = np.array([(1, 2., 'Hello'), (2, 3., "World")],
               dtype=[('foo', 'i4'), ('bar', 'f4'), ('baz', 'S10')])

recordarr = np.rec.array(arr)
pprint.pprint(recordarr)
rec.array([(1, 2., b'Hello'), (2, 3., b'World')],
          dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])

numpy.rec模块提供了多个便利的函数来创建记录数组,请参阅记录数组创建方法列表

注意:numpy.recnumpy.core.records的首选别名。

  1. core.records.array(obj[, dtype, shape, …]):从各种各样的对象构造一个记录数组。

  2. core.records.fromarrays(arrayList[, dtype, …]):从一维的list或array创建记录数组。

  3. core.records.fromrecords(recList[, dtype, …]):从文本格式的list创建一个记录数组。

  4. core.records.fromstring(datastring[, dtype, …]):从二进制字符串数据创建(只读)记录数组。

  5. core.records.fromfile(fd[, dtype, shape, …]):从二进制文件数据创建记录数组。

参考

  1. 一文彻底搞懂numpy的结构化数组——structured array

  2. Numpy的结构化数组

  3. 结构化数组

  4. 结构化数组