说明
NumPy 教程 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
「结构化数组」这一称呼来源于C语言,在 C语言中,如果我们需要创建一个「学生」的二维及多维数组,每一个学生包括 姓名、年龄、性别、体重 四个信息,我们需要先构造一个结构体,然后使用结构体数组。本页将介绍如果定义这个结构体的数据类型。
结构化数据类型可以被认为是一定长度的字节序列(结构的项大小),它被解释为字段的集合。 每个字段在结构中都有一个名称、一个数据类型和一个字节偏移量。 字段的数据类型可以是任何 numpy 数据类型,包括其他结构化数据类型,也可以是子数组数据类型,其行为类似于指定形状的 ndarray。 字段的偏移量是任意的,字段甚至可能重叠。 这些偏移量通常由 numpy 自动确定,但也可以指定。
可以使用函数 numpy.dtype 创建结构化数据类型。 有 4 种替代形式的规范,它们的灵活性和简洁性各不相同。 这些在数据类型对象 也有相应的介绍。它们是:
元组列表,每个字段一个元组。每个元组都具有 (fieldname, datatype, shape) 形式,其中 shape 是可选的。 fieldname 是一个字符串(如果使用了标题,则为元组,请参阅下面的 Field Titles),数据类型可以是任何可转换为数据类型的对象,而 shape 是指定子数组形状的整数元组。
np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])
# dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
如果 fieldname 是空字符串 '',则该字段将被赋予 f#
形式的默认名称,其中 f#
是该字段的整数索引,从左侧开始计数:
np.dtype([('x', 'f4'), ('', 'i4'), ('z', 'i8')])
# dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])
结构内字段的字节偏移量和总结构项目大小是自动确定的。
在这个速记符号中,任何字符串 dtype 规范都可以在字符串中使用并用逗号分隔。 字段的项大小和字节偏移量是自动确定的,并且字段名称被赋予默认名称 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))])
这是最灵活的规范形式,因为它允许控制字段的字节偏移量和结构的项目大小。
该字典有两个必需键,“名称”和“格式”,以及四个可选键,“偏移”、“项目大小”、“对齐”和“标题”。 ‘names’ 和 ‘formats’ 的值应分别是相同长度的字段名称列表和 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,以使自动偏移计算使用对齐的偏移(请参阅 Automatic Byte Offsets and Alignment),就好像 numpy.dtype 的“align”关键字参数已设置为 True。
可选的“titles”值应该是与“names”长度相同的标题列表,请参阅下面的字段标题。
字典的键是字段名,值是指定类型和偏移量的元组:
np.dtype({'col1': ('i1', 0), 'col2': ('f4', 1)})
# dtype([('col1', 'i1'), ('col2', '<f4')])
不鼓励这种形式,因为 Python 字典在 Python 3.6 之前的 Python 版本中不保留顺序。 可以使用 3 元组指定字段标题(Field Titles),见下文。
结构化数据类型的字段名称列表可以在 dtype 对象的 names 属性中找到:
d = np.dtype([('x', 'i8'), ('y', 'f4')])
d.names
# ('x', 'y')
字段名称可以通过使用相同长度的字符串序列分配给名称属性来修改。
dtype 对象还有一个类似字典的属性 fields,其键是字段名称(和字段标题,见下文),其值是包含每个字段的 dtype 和字节偏移量的元组。
d.fields
# mappingproxy({'x': (dtype('int64'), 0),
# 'y': (dtype('float32'), 8)})
对于非结构化数组,名称和字段属性都将等于 None。 测试 dtype 是否结构化的推荐方法是使用 if dt.names is not None
而不是 if dt.names
,以说明具有 0 个字段的 dtypes。
如果可能,结构化数据类型的字符串表示会以“元组列表”形式显示,否则 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-struct 相同的方式填充结构。 在某些情况下,对齐结构可以提高性能,但会增加数据类型的大小。 填充字节插入字段之间,以便每个字段的字节偏移量将是该字段对齐的倍数,对于简单数据类型,这通常等于字段的字节大小,请参阅 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 端进行一些工作才能获得准确的对应关系。
如果使用基于字典的 dtype 规范中的可选 offsets 键指定了偏移量,则设置 align=True
将检查每个字段的偏移量是其大小的倍数,并且项目大小是最大字段大小的倍数,并引发异常如果不。
如果结构化数组的字段偏移量和项大小满足对齐条件,则该数组将设置 ALIGNED 标志。
便利函数 numpy.lib.recfunctions.repack_fields
将对齐的 dtype 或数组转换为打包的,反之亦然。它采用 dtype 或结构化 ndarray 作为参数,并返回一个副本,其中包含重新打包的字段,有或没有填充字节。
除了字段名称之外,字段还可能具有关联的标题、备用名称,有时用作字段的附加描述或别名。 标题可用于索引数组,就像字段名称一样。
要在使用 dtype 规范的元组列表形式时添加标题,可以将字段名称指定为两个字符串的元组而不是单个字符串,这将分别是字段的标题和字段名称。 例如:
np.dtype([(('my title', 'name'), 'f4')])
# dtype([(('my title', 'name'), '<f4')])
当使用第一种形式的基于字典的规范时,标题可以作为额外的“标题”键提供,如上所述。 当使用第二个(不鼓励的)基于字典的规范时,可以通过提供 3 元素元组(datatype, offset, title)而不是通常的 2 元素元组来提供标题:
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 中实现为默认具有基本类型 numpy.void,但可以使用数据类型对象中描述的 dtype 规范的 (base_dtype, dtype) 形式将其他 numpy 类型解释为结构化类型。 这里,base_dtype 是所需的底层 dtype,字段和标志将从 dtype 复制。 这种 dtype 类似于 C 中的“union”。
更新时间:June 29, 2022, 3 p.m. 标签:numpy 结构化 数据类型