0%

NumPy数组之类型、视图/副本

这里介绍NumPy数组的类型,并说明视图与副本的区别

abstract.png

类型

数字类型

dtype参数指定创建元素的类型。而astype()方法则转换数组元素为指定类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

# dtype参数指定创建元素的类型
num_by_float64 = np.arange(5, dtype=np.float64)
print(num_by_float64)
# 输出: [0. 1. 2. 3. 4.]
print(num_by_float64.dtype)
# 输出: float64

# astype()方法:转换数组元素为指定类型
num_by_int8 = num_by_float64.astype(np.int8)
print(num_by_int8)
# 输出: [0 1 2 3 4]
print(num_by_int8.dtype)
# 输出: int8

iinfo()获取整型的最小值、最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

# iinfo(): 获取整型的最小值、最大值
uint8_min_max = np.iinfo(np.uint8)
int8_min_max = np.iinfo(np.int8)
print(f"uint8_min_max : {uint8_min_max}")
# 输出:
# uint8_min_max : Machine parameters for uint8
# ---------------------------------------------------------------
# min = 0
# max = 255
# ---------------------------------------------------------------

print(f"int8_min_max.min : {int8_min_max.min}")
# 输出: int8_min_max.min : -128
print(f"int8_min_max.max : {int8_min_max.max}")
# 输出: int8_min_max.max : 127

finfo()获取浮点型的最小值、最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

float32_min_max = np.finfo(np.float32)
print(f"float32_min_max : {float32_min_max}")
# 输出:
# float32_min_max : Machine parameters for float32
# ---------------------------------------------------------------
# precision = 6 resolution = 1.0000000e-06
# machep = -23 eps = 1.1920929e-07
# negep = -24 epsneg = 5.9604645e-08
# minexp = -126 tiny = 1.1754944e-38
# maxexp = 128 max = 3.4028235e+38
# nexp = 8 min = -max
# smallest_normal = 1.1754944e-38 smallest_subnormal = 1.4012985e-45
# ---------------------------------------------------------------

print(f"float32_min_max.min : {float32_min_max.min}")
# 输出: float32_min_max.min : -3.4028234663852886e+38

print(f"float32_min_max.max : {float32_min_max.max}")
# 输出: float32_min_max.max : 3.4028234663852886e+38

字符串类型

  • np.str_类型:固定(Unicode)字符数的Unicode字符串类型。类型的字符代码U。即:U5表示字符串最多包含5个Unicode字符
  • np.bytes_类型:固定字节数的字节字符串类型。类型的字符代码S。即:S3表示字符串最多包含3个字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

s1 = np.array(['Hello','Nature','你好我爱你','你好我讨厌你'],dtype='U5')
print(s1)
# 输出: ['Hello' 'Natur' '你好我爱你' '你好我讨厌']

s2 = np.array(['Boy','Good','你'.encode("utf-8"),'你好'.encode("utf-8")], dtype='S3')
decoded_s2 = [s.decode('utf-8') for s in s2]
print(s2)
# 输出: [b'Boy' b'Goo' b'\xe4\xbd\xa0' b'\xe4\xbd\xa0']

# "你"、"好" 使用utf-8编码后均为3个字节。故s2中 "好" 字被截断了
print(decoded_s2)
# 输出: ['Boy', 'Goo', '你', '你']

numpy 2.0支持变长字符串类型:numpy.dtypes.StringDType

1
2
3
4
5
6
7
8
import numpy as np

words = np.array( ["Hello World", "Aaron","你站在这别动哦~"], dtype=np.dtypes.StringDType())
print(f"words : {words}")
# 输出: words : ['Hello World' 'Aaron' '你站在这别动哦~']

print(f"words.dtype: {words.dtype}")
# 输出: words.dtype: StringDType()

结构化数据类型

结构化数据类型使用元组列表来定义各字段的类型,每个字段使用一个元组描述类型信息。具体地:元组形式为:(字段名称,数据类型,形状)。其中形状是可选的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np

# 定义 结构化数据类型
person_type = np.dtype( [("姓名","U10"),("年龄",np.int8),("三围",np.float32,(3,))] )

# names属性: 获取结构化数据类型的字段名称信息
print(f"person_type.names: {person_type.names}")
# 输出: person_type.names: ('姓名', '年龄', '三围')

# 根据结构化数据类型的字段名称 获取其数据类型、形状信息
print(f"person_type['姓名'] : {person_type['姓名']}")
# 输出: person_type['姓名'] : <U10

print(f"person_type['年龄'] : {person_type['年龄']}")
# 输出: person_type['年龄'] : int8

print(f"person_type['三围'] : {person_type['三围']}")
# 输出: person_type['三围'] : ('<f4', (3,))

我们可使用Python元组列表来进行初始化,然后使用位置索引 访问/修改 对应的元素;此外,还支持使用字段名称索引 访问/修改 数组中元素的相应字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np

# 定义 结构化数据类型
person_type = np.dtype( [("姓名","U10"),("年龄",np.int8),("三围",np.float32,(3,))] )

# 使用Python元组列表进行初始化
mans = np.array([("张三",17,(1,2,3)),("李四",-22,(-5,-6,-7))],dtype=person_type)
print(f"mans : {mans}, dtype: {mans.dtype}")
# 输出: mans : [('张三', 17, [ 1., 2., 3.]) ('李四', -22, [-5., -6., -7.])], dtype: [('姓名', '<U10'), ('年龄', 'i1'), ('三围', '<f4', (3,))]

# 使用位置索引 访问/修改 对应的元素。可通过Python元组进行赋值
mans[0] = ("卧龙",99,(111,222,333))
mans[1] = ("凤雏",18,(19,20,21))

print(f"卧龙 Info: {mans[0]}")
# 输出: 卧龙 Info: ('卧龙', 99, [111.0, 222.0, 333.0])
print(f"凤雏 Info :{mans[1]}")
# 输出: 凤雏 Info :('凤雏', 18, [19.0, 20.0, 21.0])

# 使用字段名称索引 访问/修改 数组中的相应字段
mans['年龄'] = 14
mans['三围'] = [(90,91,92),(80,81,82)]

print(f"mans : {mans}, dtype: {mans.dtype}")
# 输出: mans : [('卧龙', 14, [90., 91., 92.]) ('凤雏', 14, [80., 81., 82.])], dtype: [('姓名', '<U10'), ('年龄', 'i1'), ('三围', '<f4', (3,))]
print(f"姓名 Info :{mans['姓名']}")
# 输出: 姓名 Info :['卧龙' '凤雏']
print(f"三围 Info :{mans['三围']}")
# 输出:
# 三围 Info :[[90. 91. 92.]
# [80. 81. 82.]]

视图/副本

NumPy数组事实上由两部分组成:包含实际数据元素的原始数据数组、元数据信息。其中,后者提供了如何解释原始数据的相关信息。例如:数据类型、维数等。值得注意的是,对于reshape()方法而言,该方法会在条件允许的情况下创建视图,否则将创建副本

  • 视图:可通过view()方法创建视图。对于视图而言,其使用的原始数据数组和原数组是同一份。其仅通过改变元数据信息,来实现对原数组不同形式的访问。所以,下例对原数组a、视图数组a_view进行修改时,均会影响彼此
  • 副本:可通过copy()方法创建副本。对于副本而言,其会复制原数组的原始数据数组、元数据来创建一个新数组。换言之,副本与原数组,不仅元信息不是同一份,连原始数组也不是同一份。所以,下例对原数组a、副本数组a_copy进行修改时,均不会影响彼此
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np

a = np.array([[1,2,3],[4,5,6]])
a_view = a.view()
a_copy = a.copy()

a[0][0] = -520
a_view[0][1] = -996
a_copy[0][2] = -1314

print(a)
# 输出:
# [[-520 -996 3]
# [ 4 5 6]]

print(a_view)
# 输出:
# [[-520 -996 3]
# [ 4 5 6]]

print(a_copy)
# 输出:
# [[ 1 2 -1314]
# [ 4 5 6]]

可通过下述属性判断一个NumPy数组是视图还是副本:

  • base 属性: 用于判断该数组的原始数据是否来自其他数组。如果该数组是另一个数组的视图,则该属性指向源数组;如果该数组是副本,则该属性为None
  • flags.owndata 属性:用于指示该数组是否拥有自己的原始数据。如果该数组是视图,则该属性为false;如果该数组是副本,则该属性为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np

a = np.array([[1,2,3],[4,5,6]])
a_view = a.view()
a_copy = a.copy()

print(a.base)
# 输出: None

print(a_view.base)
# 输出:
# [[1 2 3]
# [4 5 6]]
print(a_view.base is a)
# 输出: True

print(a_copy.base)
# 输出: None
print(a_copy.base is a)
# 输出: False


print(a.flags.owndata)
# 输出: True
print(a_view.flags.owndata)
# 输出: False
print(a_copy.flags.owndata)
# 输出: True
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝