Numpy 数据类型与结构化数组

数据类型

NumPy 支持的数值类型比 Python 多得多。本节显示哪些可用,以及如何修改数组的数据类型。

支持的原始类型与 C 中的类型密切相关:

Numpy 类型C型描述
0numpy.bool_bool存储为字节的布尔值(真或假)
1numpy.bytesigned char平台定义
2numpy.ubyteunsigned char平台定义
3numpy.shortshort平台定义
4numpy.ushortunsigned short平台定义
5numpy.intcint平台定义
6numpy.uintcunsigned int平台定义
7numpy.int_long平台定义
8numpy.uintunsigned long平台定义
9numpy.longlonglong long平台定义
10numpy.ulonglongunsigned long long平台定义
11numpy.half / numpy.float16NaN半精度浮点数:符号位,5 位指数,10 位尾数
12numpy.singlefloat平台定义的单精度浮点数:通常为符号位、8 位指数、23 位尾数
13numpy.doubledouble平台定义的双精度浮点数:通常为符号位、11 位指数、52 位尾数。
14numpy.longdoublelong double平台定义的扩展精度浮点数
15numpy.csinglefloat complex复数,由两个单精度浮点数表示(实部和虚部)
16numpy.cdoubledouble complex复数,由两个双精度浮点数(实部和虚部)表示。
17numpy.clongdoublelong double complex复数,由两个扩展精度浮点数(实部和虚部)表示。

由于其中许多具有依赖于平台的定义,因此提供了一组固定大小的别名(请参阅大小别名)。

NumPy 数字类型是dtype(数据类型)对象的实例,每个对象都有独特的特征。使用导入 NumPy 后

>>> import numpy as np

在dtypes可作为np.bool_,np.float32等等。

上面未列出的高级类型在后面的结构化数组部分进行了探讨。

有 5 种基本数字类型表示布尔值 (bool)、整数 (int)、无符号整数 (uint) 浮点数 (float) 和复数。名称中带有数字的那些表示类型的位大小(即表示内存中的单个值需要多少位)。某些类型(例如int和 intp)具有不同的位大小,具体取决于平台(例如 32 位与 64 位机器)。在与寻址原始内存的低级代码(例如 C 或 Fortran)接口时,应考虑到这一点。

数据类型可以用作将 python 数字转换为数组标量的函数(有关解释,请参阅数组标量部分),将数字的 python 序列转换为该类型的数组,或作为许多 numpy 函数或方法接受的 dtype 关键字的参数. 一些例子:

>>> import numpy as np
>>> x = np.float32(1.0)
>>> x
1.0
>>> y = np.int_([1,2,4])
>>> y
array([1, 2, 4])
>>> z = np.arange(3, dtype=np.uint8)
>>> z
array([0, 1, 2], dtype=uint8)

数组类型也可以通过字符代码引用,主要是为了保持与旧包(如 Numeric)的向后兼容性。一些文档可能仍然引用这些,例如:

>>> np.array([1, 2, 3], dtype='f')
array([ 1.,  2.,  3.], dtype=float32)

我们建议改用 dtype 对象。

要转换数组的类型,请使用 .astype() 方法(首选)或类型本身作为函数。例如:

>>> z.astype(float)                 
array([  0.,  1.,  2.])
>>> np.int8(z)
array([0, 1, 2], dtype=int8)

请注意,在上面,我们使用Python浮点对象作为 dtype。NumPy的人都知道int是指np.int_,bool手段np.bool_,那float是np.float_和complex是np.complex_。其他数据类型没有 Python 等效项。

要确定数组的类型,请查看 dtype 属性:

>>> z.dtype
dtype('uint8')

dtype 对象还包含有关类型的信息,例如其位宽和字节顺序。数据类型也可以间接用于查询该类型的属性,例如是否为整数:

>>> d = np.dtype(int)
>>> d
dtype('int32')

>>> np.issubdtype(d, np.integer)
True

>>> np.issubdtype(d, np.floating)
False

数组标量

NumPy 通常将数组元素作为数组标量(具有关联 dtype 的标量)返回。数组标量与 Python 标量不同,但在大多数情况下,它们可以互换使用(主要的例外是 v2.x 之前的 Python 版本,其中整数数组标量不能作为列表和元组的索引)。有一些例外,例如当代码需要标量的非常特定的属性时,或者当它专门检查一个值是否是 Python 标量时。通常,通过使用相应的 Python 类型函数(例如,int、float、complex、str、unicode)将数组标量显式转换为 Python 标量,可以轻松解决问题。

使用数组标量的主要优点是它们保留了数组类型(Python 可能没有可用的匹配标量类型,例如int16)。因此,使用数组标量可确保数组和标量之间的行为相同,无论该值是否在数组内。NumPy 标量也有许多与数组相同的方法。

溢出错误

当值需要比数据类型中可用的更多内存时,NumPy 数字类型的固定大小可能会导致溢出错误。例如, 对于 64 位整数numpy.power计算正确,但对于 32 位整数给出 1874919424(不正确)。100 ** 8

np.power(100, 8, dtype=np.int64)
10000000000000000
np.power(100, 8, dtype=np.int32)
1874919424

NumPy 和 Python 整数类型的行为在整数溢出方面存在显着差异,并且可能会使期望 NumPy 整数行为类似于 Python 的int. 与 NumPy 不同,Python 的大小int是灵活的。这意味着 Python 整数可以扩展以容纳任何整数并且不会溢出。

NumPy 提供numpy.iinfo并numpy.finfo分别验证 NumPy 整数和浮点值的最小值或最大值

>>> np.iinfo(int) # Bounds of the default integer on this system.
iinfo(min=-9223372036854775808, max=9223372036854775807, dtype=int64)
>>> np.iinfo(np.int32) # Bounds of a 32-bit integer
iinfo(min=-2147483648, max=2147483647, dtype=int32)
>>> np.iinfo(np.int64) # Bounds of a 64-bit integer
iinfo(min=-9223372036854775808, max=9223372036854775807, dtype=int64)

如果 64 位整数仍然太小,则结果可能会转换为浮点数。浮点数提供了更大但不精确的可能值范围。

np.power(100, 100, dtype=np.int64) # Incorrect even with 64-bit int
0
np.power(100, 100, dtype=np.float64)
1e+200

扩展精度

Python 的浮点数通常是 64 位浮点数,几乎相当于np.float64. 在一些不寻常的情况下,使用更精确的浮点数可能会很有用。这在 numpy 中是否可行取决于硬件和开发环境:具体来说,x86 机器提供具有 80 位精度的硬件浮点,虽然大多数 C 编译器都提供这种 类型,但 MSVC(Windows 构建的标准)使得 等同于(64 位)。NumPy 使编译器可用作(以及 用于复数)。您可以找出 numpy 提供的内容。long doublelong doubledoublelong doublenp.longdoublenp.clongdoublenp.finfo(np.longdouble)

NumPy 不提供比 C 的\更精确的 dtype ;尤其是 128 位 IEEE 四精度数据类型(FORTRAN 的\)不可用。long doubleREAL*16

为了有效的内存对齐,np.longdouble通常用零位填充存储,要么是 96 位要么是 128 位。哪个更高效取决于硬件和开发环境;通常在 32 位系统上它们被填充到 96 位,而在 64 位系统上它们通常被填充到 128 位。np.longdouble填充到系统默认值;np.float96并np.float128为需要特定填充的用户提供。尽管有这些名称,np.float96并且 np.float128只提供与 一样多的精度np.longdouble,即大多数 x86 机器上的 80 位和标准 Windows 版本中的 64 位。

请注意,即使np.longdouble提供比 python 更高的精度float,也很容易失去额外的精度,因为 python 经常强制值通过float。例如,%格式化操作符要求将其参数转换为标准的 Python 类型,因此即使需要许多小数位,也无法保留扩展精度。使用值测试您的代码会很有用 。

结构化数组

结构化数组是 ndarrays,其数据类型是组织为命名字段序列的更简单数据类型的组合。

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

这x是一个长度为 2 的一维数组,其数据类型是一个具有三个字段的结构:1. 长度为 10 或更少的名为“name”的字符串,2. 一个名为“age”的 32 位整数,以及 3. a 32 - 位浮点数名为“重量”。

如果x在位置 1 处建立索引,则会得到一个结构:

>>> x[1]
('Fido', 3, 27.0)

您可以通过使用字段名称进行索引来访问和修改结构化数组的各个字段:

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

结构化数据类型旨在能够模仿 C 语言中的“结构”,并共享类似的内存布局。它们用于与 C 代码接口和结构化缓冲区的低级操作,例如用于解释二进制 blob。出于这些目的,它们支持特殊功能,例如子数组、嵌套数据类型和联合,并允许控制结构的内存布局。

希望操作表格数据(例如存储在 csv 文件中)的用户可能会发现其他 pydata 项目更合适,例如 xarray、pandas 或 DataArray。这些为表格数据分析提供了高级界面,并且针对该用途进行了更好的优化。例如,相比之下,numpy 中结构化数组的类似 C 结构的内存布局会导致缓存行为不佳。

结构化数据类型

可以使用函数创建结构化数据类型numpy.dtype。有 4 种替代形式的规范,它们的灵活性和简洁性各不相同。这些在数据类型对象参考页面中有进一步的记录 ,总而言之,它们是:

  1. 元组列表,每个字段一个元组
    每个元组都具有其中 shape 是可选的形式。是一个字符串(或元组,如果使用标题,请参阅 下面的字段标题),可以是任何可转换为数据类型的对象,并且是指定子数组形状的整数元组。(fieldname, datatype, shape)fieldnamedatatypeshape
np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])

dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])

如果fieldname是空字符串’’,则该字段将被赋予表单的默认名称f#,其中#是该字段的整数索引,从左侧开始计数:

np.dtype([('x', 'f4'), ('', 'i4'), ('z', 'i8')])
dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])

结构中字段的字节偏移量和总结构项大小是自动确定的。
2. 一串逗号分隔的数据类型规范
在这个速记符号中,任何字符串 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))])
  1. 字段参数数组字典
    这是最灵活的规范形式,因为它允许控制字段的字节偏移量和结构的项大小。

字典有两个必需的键,“名称”和“格式”,以及四个可选键,“偏移”、“项目大小”、“对齐”和“标题”。‘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使自动偏移计算使用对齐的偏移(请参阅自动字节偏移和对齐),就好像 的 ‘align’ 关键字参数numpy.dtype已设置为 True。

可选的“titles”值应该是与“names”长度相同的标题列表,请参阅下面的字段标题。
4. 字段名称字典
不鼓励使用这种形式的规范,但在此处记录,因为较旧的 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)})

对于非结构化数组,thenames和fieldsattributes 都将相等None。测试dtype是否结构化的推荐方法是使用if 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 规范中的可选键指定偏移量,则设置align=True将检查每个字段的偏移量是否是其大小的倍数,以及 itemsize 是否是最大字段大小的倍数,如果不是,则引发异常。

如果结构化数组的字段和itemsize的偏移量满足对齐条件,则该数组将具有ALIGNED flag集合。

便利函数numpy.lib.recfunctions.repack_fields将对齐的 dtype 或数组转换为压缩类型,反之亦然。它采用 dtype 或结构化 ndarray 作为参数,并返回带有重新打包的字段的副本,带有或不带有填充字节。

字段标题
除了字段名称之外,字段还可能有一个相关联的title,一个备用名称,有时用作字段的附加描述或别名。标题可用于索引数组,就像字段名称一样。

要在使用 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 中实现为具有基本类型 ,但可以使用数据类型对象中描述的 dtype 规范形式 将其他 numpy 类型解释为结构化类型。这里,是所需的底层 dtype,字段和标志将从 . 此 dtype 类似于 C 中的“联合”。(base_dtype, dtype)base_dtypedtype

结构化数组的索引和赋值

将数据分配给结构化数组

有多种方法可以为结构化数组赋值:使用 python 元组、使用标量值或使用其他结构化数组。

从 Python 本机类型(元组)赋值

为结构化数组赋值的最简单方法是使用 python 元组。每个分配的值应该是一个长度等于数组中字段数的元组,而不是列表或数组,因为它们会触发 numpy 的广播规则。元组的元素被分配给数组的连续字段,从左到右:

>>> x = np.array([(1, 2, 3), (4, 5, 6)], dtype='i8, f4, f8')
>>> x[1] = (7, 8, 9)
>>> x
array([(1, 2., 3.), (7, 8., 9.)],
     dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '<f8')])

从标量赋值

分配给结构化元素的标量将分配给所有字段。当将标量分配给结构化数组或将非结构化数组分配给结构化数组时,就会发生这种情况:

>>> x = np.zeros(2, dtype='i8, f4, ?, S1')
>>> x[:] = 3
>>> x
array([(3, 3., True, b'3'), (3, 3., True, b'3')],
      dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
>>> x[:] = np.arange(2)
>>> x
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 array data 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
>>> b
array([(0., b'0.0', b''), (0., b'0.0', b''), (0., b'0.0', b'')],
      dtype=[('x', '<f4'), ('y', 'S3'), ('z', 'O')])

后续内容太多了,但用的没有那么频繁。有兴趣建议看官网

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ACxz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值