1.介绍

结构数组是NumPy中的一种高级数据结构,它允许用户在单个数组中存储多种数据类型的元素。与普通的NumPy数组不同,结构数组的每个元素可以具有不同的数据类型,并且可以使用字段名来引用这些元素。这使得结构数组非常适合处理表格数据、数据库查询结果以及其他复杂数据结构。

2. 创建数组

2.1 字符串式声明

import numpy as np

if __name__ == '__main__':
# 定义类型
dt = "U10,i4,f"
# 创建数组
arr = np.array([
[("Go", 2, 8.5)],
[("Java", 3, 8.0)],
[("Python", 1, 9.0)],
], dtype=dt)
print(arr)

"""
[[('Go', 2, 8.5)]
[('Java', 3, 8. )]
[('Python', 1, 9. )]]
"""

@注:字符串式声明比较简单,只要使用逗号隔开即可,如上述示例:i1,i4,f

2.2 元组列表式声明

a. 声明类型:

dt = np.dtype([("字段名", "数据类型"),...,("字段名n", "数据类型n")])  

b. 使用示例:

import numpy as np

if __name__ == '__main__':
# 定义类型
dt = np.dtype([("name", "U10"), ("age", int), ("address", "U20"), ("weight", float)])
# 创建数组
arr = np.array([
[("张三", 20, "北京昌平区", 74.5)],
[("小花", 18, "上海徐汇区", 48.5)]
], dtype=dt)
print("创建数组:\n", arr)
print("访问arr[0]:\n", arr[0])
print("访问arr['name']:\n", arr['name'])
print("访问arr['age']:\n", arr['age'])

"""
创建数组:
[[('张三', 20, '北京昌平区', 74.5)]
[('小花', 18, '上海徐汇区', 48.5)]]
访问arr[0]:
[('张三', 20, '北京昌平区', 74.5)]
访问arr['name']:
[['张三']
['小花']]
访问arr['age']:
[[20]
[18]]
"""

2.3 字段标题

元组列表除了上面的使用示例,还可以给字段加上标题,使用方式和语法如下:

a. 声明类型:

dt = np.dtype([(("标题", "字段名"), "数据类型"), ... ,(("标题n", "字段名n"), "数据类型")])  

b. 使用示例:

import numpy as np

if __name__ == '__main__':
# 定义类型
dt = np.dtype([(("姓名", "name"), "U10"), (("年龄", "age"), int), (("体重", "weight"), float)])
# 创建数组
arr = np.array([
[("张三", 28, 80.23)],
[("小明", 16, 66.55)],
], dtype=dt)
print("创建数组:\n", arr)
print("访问 字段标题-> \n", arr["姓名"])
print("访问 字段名-> \n", arr["name"])

"""
创建数组:
[[('张三', 28, 80.23)]
[('小明', 16, 66.55)]]
访问 字段标题->
[['张三']
['小明']]
访问 字段名->
[['张三']
['小明']]
"""

2.4 字典式声明

a. 声明类型:

dt = {'names':('字段1',...'字段2'), 'formats':('类型1',...,'类型n')}

b. 使用示例:

import numpy as np

if __name__ == '__main__':
# 定义类型
dt = {"names": ("name", "age", "address", "weight"), "formats": ("U10", "i", "U20", "f")}
# 创建数组
arr = np.array([
[("张三", 20, "北京昌平区", 74.5)],
[("小花", 18, "上海徐汇区", 48.5)]
], dtype=dt)
print("创建数组:\n", arr)
print("访问arr[0]:\n", arr[0])
print("访问arr['name']:\n", arr['name'])
print("访问arr['age']:\n", arr['age'])

"""
创建数组:
[[('张三', 20, '北京昌平区', 74.5)]
[('小花', 18, '上海徐汇区', 48.5)]]
访问arr[0]:
[('张三', 20, '北京昌平区', 74.5)]
访问arr['name']:
[['张三']
['小花']]
访问arr['age']:
[[20]
[18]]
"""

3. 类型说明

3.1 类型汇总

以下是NumPy结构数组的常见数据类型的列表,包括类型、字符代码和说明。

类型 字符代码 说明
int ‘i’ 整数数据类型
int8 ‘i1’ 8位有符号整数类型
int16 ‘i2’ 16位有符号整数类型
int32 ‘i4’ 32位有符号整数类型
int64 ‘i8’ 64位有符号整数类型
uint8 ‘u1’ 8位无符号整数类型
uint16 ‘u2’ 16位无符号整数类型
uint32 ‘u4’ 32位无符号整数类型
uint64 ‘u8’ 64位无符号整数类型
float ‘f’ 浮点数数据类型
float16 ‘f2’ 16位浮点数类型
float32 ‘f4’ 32位浮点数类型
float64 ‘f8’ 64位浮点数类型
complex ‘c’ 复数数据类型
complex64 ‘c8’ 64位复数类型
complex128 ‘c16’ 128位复数类型
bool ‘b’ 布尔值数据类型
object ‘O’ 通用对象数据类型
string ‘S’ 字符串数据类型
unicode ‘U’ Unicode字符串数据类型
void ‘V’ 通用无类型数据类型
datetime ‘M’ 日期和时间数据类型
timedelta ‘m’ 时间间隔数据类型

数据类型的字符代码,用在NumPy中,表示对应的数据类型。

3.2 时间类型使用

import numpy as np

if __name__ == '__main__':
# 定义类型
dt = [
('day', 'datetime64[D]'), # 表示日期,精确到天
('minutes', 'datetime64[m]'), # 表示时间,精确到分钟
('second', 'datetime64[s]') # 表示时间,精确到秒
]
arr = np.array([
('2023-09-25', '2023-09-25T14:30', '2023-09-25T14:30:00'),
('2023-09-25', '2023-09-25T17:30', '2023-09-25T14:30:45')
], dtype=dt)
print("arr: ", arr)
print("计算时间差:", arr['minutes'][1] - arr['minutes'][0])
print("计算时间差:", arr['second'][1] - arr['second'][0])

"""
arr: [('2023-09-25', '2023-09-25T14:30', '2023-09-25T14:30:00')
('2023-09-25', '2023-09-25T17:30', '2023-09-25T14:30:45')]
计算时间差: 180 minutes
计算时间差: 45 seconds
"""

'datetime64[D]:'精确到天,而'datetime64[m]:',精确到分钟,datetime64[s]: 精确到秒

4. 字符串

Numpy使用字符串类型时,常见会是这种格式S10、U20等,其中的10,20指的就是字符的长度,单位是字节。

4.1 对比示例

NumPy结构数组中,使用字符串类型时一定要指定长度,因为字符串类型,需要在内存中预先分配内存,换句话说如果你不指定长度,那么值是存不进去的,如下示例:

import numpy as np

if __name__ == '__main__':
# 定义字符串--不指定长度
arr = np.array([
[("张三", 80.5)],
[("李四", 67.5)],
], dtype="U,f")
print("定义字符串--不指定长度: \n", arr)
# 定义字符串--指定长度
arr2 = np.array([
[("李白", 90)],
[("苏轼", 88)],
], dtype="U10,f")
print("定义字符串--指定长度: \n", arr2)

"""
定义字符串--不指定长度:
[[('', 80.5)]
[('', 67.5)]]
定义字符串--指定长度:
[[('张三', 80.5)]
[('李四', 67.5)]]
"""

4.2 原因汇总

使用字符串指定长度的原因,除了上面说的内存预分配的原因,还有其他原因,下面进行了汇总:

  1. 内存分配:字符串类型的长度决定了每个字符串在内存中占用的空间大小。如果不指定长度,NumPy无法确定要为每个字符串分配多少内存,这会导致内存分配错误或不确定性,可能会破坏数据的完整性。
  2. 数据存储:在结构数组中,每个元素都有固定的字节大小,这包括字符串字段。指定字符串长度允许NumPy为每个元素的字符串字段分配相应大小的内存块,确保数据存储的正确性。
  3. 数据对齐:结构数组中的字段通常需要按照字节边界对齐,以提高内存访问效率。指定字符串长度有助于确保每个字符串字段与其他字段正确对齐,以防止不必要的内存开销和性能下降。
  4. 数据操作:指定字符串长度还有助于NumPy正确执行数据操作,如拷贝、切片和比较。没有指定长度的字符串可能会导致不明确的行为或错误。

4.3 S、U的区别

NumPy中,字符串类型有两种常见的类型,分别是S(string)(字节字符串)和U(Unicode)(字符串)。这两种类型在处理字符串数据时具有不同的特性和用途,以下是它们的对比:

  1. S 字符串(字节字符串)
    • 字符编码:S字符串使用字节编码,通常用于处理ASCII字符集。这意味着它不支持多语言字符或特殊字符,只能表示ASCII字符。
    • 存储效率S字符串在内存中以字节为单位存储,因此在存储方面比U字符串更加紧凑,但只能表示有限的字符集。
    • 示例'S10' 表示包含最多10个ASCII字符的S字符串,例如 'Hello'
    • 适用场景:当你知道字符串只包含ASCII字符或需要节省内存时,可以选择使用S字符串。常见用途包括文件名、用户名、产品代码等。
  2. U 字符串(Unicode字符串)
    • 字符编码U字符串使用Unicode编码,支持多语言字符、特殊字符和表情符号等。它更加通用,适用于处理各种语言的文本。
    • 存储效率U字符串在内存中以Unicode字符的形式存储,因此通常会占用更多的内存。但它具有更广泛的字符支持。
    • 示例'U20' 表示包含最多20个Unicode字符的U字符串,例如 '你好,世界'
    • 适用场景:当你需要处理多语言字符、特殊字符、国际化文本或包含Unicode字符的数据时,通常应选择使用U字符串。它适用于Web应用、国际化应用程序、文本处理等场景。

@注:当你需要存储中文字符时, 使用Unicode

5. 字节顺序

5.1 概念说明

大端字节顺序(Big-Endian)和小端字节顺序(Little-Endian)是两种用于存储多字节数据(如整数、浮点数)的不同方式。它们决定了在内存中多字节数据的字节存储顺序。

5.2 大端字节顺序

在大端字节顺序中,数据的高位字节(Most Significant Byte,MSB)存储在内存的低地址处,而低位字节(Least Significant Byte,LSB)存储在内存的高地址处。这就好像你阅读英文文本,从左到右逐个字母阅读一样。

示例: 假设我们要存储整数值0x1234(十进制为4660),在大端字节顺序下,在内存中的存储方式是:

高地址 --> 0x12 | 0x34 <-- 低地址

这意味着0x12(高位字节)存储在较低的内存地址,0x34(低位字节)存储在较高的内存地址。

5.3 小端字节顺序

在小端字节顺序中,数据的低位字节(LSB)存储在内存的低地址处,而高位字节(MSB)存储在内存的高地址处。这就好像你逆序阅读英文文本,从右到左逐个字母阅读一样。

示例: 同样,我们要存储整数值0x1234。在小端字节顺序下,在内存中的存储方式是:

高地址 --> 0x34 | 0x12 <-- 低地址

这意味着0x34(低位字节)存储在较低的内存地址,0x12(高位字节)存储在较高的内存地址。

5.4 字节顺序重要性

为什么字节顺序很重要?因为不同的硬件架构可能使用不同的字节顺序,而且在数据交换和文件传输等场景中,正确的字节顺序非常关键,否则数据可能会被错误地解释或损坏。

大多数现代计算机采用小端字节顺序,例如x86架构的计算机。但某些其他体系结构,如某些PowerPCARM架构,采用大端字节顺序。因此,在处理二进制数据时,特别是与不同架构的系统进行数据交换时,了解和处理字节顺序是非常重要的。

5.5 Numpy中使用

NumPy中,默认情况下,多字节数据类型的字节顺序是与计算机的硬件平台相关的,即它会自动选择适合硬件平台的字节顺序。这被称为 本地字节顺序。你也可以使用以下方法来显式指定字节顺序:

  • >:指定大端字节顺序。
  • <:指定小端字节顺序。

例如,在创建结构数组或使用特定数据类型时,你可以明确指定字节顺序,如下所示:

import numpy as np

# 创建一个16位整数数组,指定大端字节顺序
arr_big_endian = np.array([1, 2, 3], dtype='>i2')

# 创建一个32位浮点数数组,指定小端字节顺序
arr_little_endian = np.array([1.0, 2.0, 3.0], dtype='<f4')

在不同的应用场景中,特定的字节顺序可能会受到影响,尤其是在处理二进制数据或与其他系统进行交互时。因此,了解和控制字节顺序是非常重要的。

微信搜索【猿码记】查看更多文章