NumPy学习3
继续学习NumPy,今天学习以下3个章节:
7,NumPy高级索引
8,NumPy广播机制
9,NumPy遍历数组
numpy_test3.py :
import numpy as np ''' 7, NumPy高级索引 NumPy 与 Python 的内置序列相比,它提供了更多的索引方式。 在 NumPy 中还可以使用高级索引方式,比如整数数组索引、布尔索引以及花式索引 高级索引返回的是数组的副本(深拷贝),而切片操作返回的是数组视图(浅拷贝)。 1) 整数数组索引 整数数组索引,它可以选择数组中的任意一个元素,比如,选择第几行第几列的某个元素, ''' print("-----------整数数组索引-------------") #创建二维数组 arr1 = np.array([[1, 3], [5, 7], [9, 6]]) print('arr1 :', arr1) #[0,1,2]代表行索引;[1,1,0]代表列索引 arr2 = arr1[[0,1,2],[1,1,0]] print('arr2 :', arr2) ''' arr1 : [ [1 3] [5 7] [9 6] ] arr2 : [3 7 9] 分析:将行、列索引组合会得到 (0,1)、(1,1) 和 (2,0) ,它们分别对应着输出结果在原数组中的索引位置。 ''' #获取了 4*4 数组中的四个角上元素,它们对应的行索引是 [0,0] 和 [3,3],列索引是 [0,3] 和 [0,3] arr3 = np.array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [ 12, 13, 14, 15]]) print('arr3 :', arr3) row = np.array([[0,0],[3,3]]) col = np.array([[0,3],[0,3]]) #获取四个角的元素 arr4 = arr3[row,col] print('arr4 :', arr4) ''' arr3 : [ [ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] ] arr4 : [ [ 0 3] [12 15] ] 分析:将行、列索引组合会得到 (0,0)、(0,3) 和 (3,0)、(3,3) ,它们分别对应着输出结果在原数组中的索引位置。 ''' ''' 也可以将切片所使用的:或省略号...与整数数组索引结合使用 ''' print("-----------将切片所使用的:或省略号...与整数数组索引结合使用-------------") #对行列分别进行切片 arr5 = arr3[1:3,1:3] print('arr5 :', arr5) #行使用基础索引,对列使用高级索引 arr6 = arr3[1:3,[1,2]] #显示切片后结果 print('arr6 :', arr6) #对行使用省略号... arr7 = arr3[...,2:] print('arr7 :', arr7) #对列使用省略号... arr8 = arr3[2:,...] print('arr8 :', arr8) ''' arr3 : [ [ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] ] arr5 : [ [ 5 6] [ 9 10] ] arr6 : [ [ 5 6] [ 9 10] ] arr7 : [ [ 2 3] [ 6 7] [10 11] [14 15] ] arr8 : [ [ 8 9 10 11] [12 13 14 15] ] ''' ''' 2) 布尔数组索引 当输出的结果需要经过布尔运算(如比较运算)时,此时会使用到另一种高级索引方式,即布尔数组索引。 ''' print("-----------布尔数组索引-------------") arr9 = np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]) print('arr9 :', arr9) # 返回所有大于5的数字组成的数组 print('arr9[arr9 > 5] :', arr9[arr9 > 5]) ''' arr9 : [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] arr9[arr9 > 5] : [ 6 7 8 9 10 11] ''' #可以使用补码运算符来去除 NaN(即非数字元素) arr10 = np.array([np.nan, 1, 2, np.nan, 3, np.nan, 5, 6, 7]) print('arr10 :', arr10) print('arr10[~np.isnan(arr10)] :', arr10[~np.isnan(arr10)]) #删除数组中非复数元素 arr11 = np.array([1, 2+3j, 3, 5, 3.2+4j, 3.2]) print('arr11 :', arr11) print('arr11[np.iscomplex(arr11)] :', arr11[np.iscomplex(arr11)]) ''' arr10 : [nan 1. 2. nan 3. nan 5. 6. 7.] arr10[~np.isnan(arr10)] : [1. 2. 3. 5. 6. 7.] arr11 : [1. +0.j 2. +3.j 3. +0.j 5. +0.j 3.2+4.j 3.2+0.j] arr11[np.iscomplex(arr11)] : [2. +3.j 3.2+4.j] ''' ''' 3) 花式索引(拓展知识) 花式索引也可以理解为整数数组索引,但是它们之间又略有不同,花式索引也会生成一个新的副本。 如果原数组是二维数组,那么索引数组也需要是二维的,索引数组的元素值与被索引数组的每一行相对应 ''' print("-----------花式索引-------------") arr12 = np.arange(32).reshape((8, 4)) print('arr12 :', arr12) # 分别对应 第3行数据、第2行数据、第1行数据、第6行数据项 arr13 = arr12[[3, 2, 1, 6]] print('arr13 :', arr13) # 也可以使用倒序索引数组 arr14 = arr12[[-3, -2, -1, -6]] print('arr14 :', arr14) # 还可以同时使用多个索引数组,但这种情况下需要添加np.ix_ arr15 = arr12[np.ix_([1, 3, 4, 2], [0, 3, 1, 2])] print('arr15 :', arr15) ''' arr12 : [[ 0 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]] arr13 : [[12 13 14 15] [ 8 9 10 11] [ 4 5 6 7] [24 25 26 27]] arr14 : [[20 21 22 23] [24 25 26 27] [28 29 30 31] [ 8 9 10 11]] arr15 : [[ 4 7 5 6] [12 15 13 14] [16 19 17 18] [ 8 11 9 10]] 其中 [1,3,4,2] 代表行索引,而 [0,3,1,2] 表示与行索引相对应的列索引值,也就是行中的元素值会按照列索引值排序。 比如,第一行元素,未排序前的顺序是 [4,5,6,7],经过列索引排序后变成了 [4,7,5,6]。 ''' ''' 8,NumPy广播机制 NumPy 中的广播机制(Broadcast)旨在解决不同形状数组之间的算术运算问题。 我们知道,如果进行运算的两个数组形状完全相同,它们直接可以做相应的运算。 ''' print("-----------NumPy广播机制-------------") a1 = np.array([0.1, 0.2, 0.3, 0.4]) b1 = np.array([10, 20, 30, 40]) c1 = a1 * b1 print('c1 :', c1) # c1 : [ 1. 4. 9. 16.] ''' 但如果两个形状不同的数组呢?它们之间就不能做算术运算了吗?当然不是!为了保持数组形状相同,NumPy 设计了一种广播机制, 这种机制的核心是对形状较小的数组,在横向或纵向上进行一定次数的重复,使其与形状较大的数组拥有相同的维度。 当进行运算的两个数组形状不同,Numpy 会自动触发广播机制。 ''' a2 = np.array([[0, 0, 0], [5, 10, 20], [10, 20, 30]]) # b数组与a数组形状不同 b2 = np.array([1, 2, 3]) c2 = a2 * b2 print('c2 :', c2) ''' c2 : [[ 0 0 0] [ 5 20 60] [10 40 90]] 3x3 的二维 a2 数组 与 1x3 的一维 b2 数组相乘,本质上可以理解为 b2 数组在纵向上向下拓展 3 次(将第一行重复 3 次), b2 变成了 [[1, 2, 3], [1, 2, 3], [1, 2, 3]] 从而生成与 a2 数组相同形状的数组,之后再与 a2 数组进行运算。 ''' ''' 9, NumPy遍历数组 NumPy 提供了一个 nditer 迭代器对象,它可以配合 for 循环完成对数组元素的遍历。 ''' print("-----------NumPy遍历数组-------------") arr16 = np.arange(0, 45, 5) print('arr16 :', arr16) arr17 = arr16.reshape(3, 3) print('arr17 :', arr17) # 使用nditer迭代器,并使用for进行遍历 for x in np.nditer(arr17): print(x, end=",") print("\n------------------------") ''' arr16 : [ 0 5 10 15 20 25 30 35 40] arr17 : [[ 0 5 10] [15 20 25] [30 35 40]] 0,5,10,15,20,25,30,35,40, ''' ''' 1) 遍历顺序 在内存中,Numpy 数组提供了两种存储数据的方式,分别是 C-order(行优先顺序)与 Fortrant-order(列优先顺序)。 那么 nditer 迭代器又是如何处理具有特定存储顺序的数组呢?其实它选择了一种与数组内存布局一致的顺序, 之所以这样做,是为了提升数据的访问效率。 在默认情况下,当我们遍历数组中元素的时候,不需要考虑数组的存储顺序,这一点可以通过遍历上述数组的转置数组来验证。 ''' print("-----------遍历顺序-------------") # 转置数组 arr18 = arr17.T print('arr18 :', arr18) for x in np.nditer(arr18): print(x, end=",") print("\n------------------------") ''' arr18 : [[ 0 15 30] [ 5 20 35] [10 25 40]] 0,5,10,15,20,25,30,35,40, 可以看出,输出结果可以看出,arr17 和 arr17.T 转置数组,的遍历顺序是一样的, 也就是说,它们在内存中的存储顺序是一样的。 ''' # 转置数组,并copy方法生成数组副本,存储方式变更 arr19 = arr17.T.copy(order='C') print('arr19 :', arr19) for x in np.nditer(arr19): print(x, end=",") print("\n------------------------") ''' arr19 : [[ 0 15 30] [ 5 20 35] [10 25 40]] 0,15,30,5,20,35,10,25,40, ''' ''' 2) 指定遍历顺序 您可以通过 nditer 对象的order参数来指定数组的遍历的顺序。 ''' print("-----------指定遍历顺序-------------") print(" C-order(行优先顺序)") for x in np.nditer(arr17, order='C'): print(x, end=",") print("\n------------------------") print(" F-order(列优先顺序)") for x in np.nditer(arr17, order='F'): print(x, end=",") print("\n------------------------") ''' C-order(行优先顺序) 0,5,10,15,20,25,30,35,40, ------------------------ F-order(列优先顺序) 0,15,30,5,20,35,10,25,40, ''' ''' 3) 修改数组元素值 nditer 对象提供了一个可选参数op_flags,它表示能否在遍历数组时对元素进行修改。它提供了三种模式,如下所示: (1) read-only 只读模式,在这种模式下,遍历时不能修改数组中的元素。 (2) read-write 读写模式,遍历时可以修改元素值。 (3) write-only 只写模式,在遍历时可以修改元素值。 ''' print("-----------遍历过程,修改数组元素值-------------") arr20 = arr17.copy() print("原数组是:", arr20) for x in np.nditer(arr20, op_flags=['readwrite']): x[...] = 2*x # 每个元素都乘2 print('修改后的数组是:', arr20) ''' 原数组是: [[ 0 5 10] [15 20 25] [30 35 40]] 修改后的数组是: [[ 0 10 20] [30 40 50] [60 70 80]] ''' ''' 4) 外部循环使用 nditer 对象的构造函数有一个“flags”参数,它可以接受以下参数值: flags参数说明 参数值 描述说明 c_index 可以跟踪 C 顺序的索引。 f_index 可以跟踪 Fortran 顺序的索引。 multi_index 每次迭代都会跟踪一种索引类型。 external_loop 返回的遍历结果是具有多个值的一维数组。 ''' print("-----------外部循环使用-------------") arr20 = arr17.copy() print("原数组是:", arr20) #修改后数组 print("修改后的一维数组") for x in np.nditer(arr20, flags=['external_loop'], order='F'): print(x) print("\n------------------------") ''' 原数组是: [[ 0 5 10] [15 20 25] [30 35 40]] 修改后的一维数组 [ 0 15 30] [ 5 20 35] [10 25 40] ''' ''' 5) 迭代多个数组 如果两个数组都能够被广播,那么 nditer 对象就可以同时对它们迭代。 假设数组 a3 的维度是 3*3,另一个数组 b3 的维度是 1*3 (即维度较小的数组 b3 可以被广播到数组 a3 中) ''' print("-----------迭代多个数组,广播迭代-------------") a3 = np.arange(0, 45, 5).reshape(3, 3) print("a3 : ", a3) b3 = np.array([1, 2, 3], dtype=int) print("b3 : ", b3) # 广播迭代 for x,y in np.nditer([a3, b3]): print("%d:%d" % (x, y), end="; ") print("\n------------------------") ''' a3 : [[ 0 5 10] [15 20 25] [30 35 40]] b3 : [1 2 3] 0:1; 5:2; 10:3; 15:1; 20:2; 25:3; 30:1; 35:2; 40:3; '''