1.4.5 大型数组运算
在实际应用中,我们经常需要在大数据集(比如数组或网格)上执行运算。
涉及数组的重量级运算,可以使用NumPy库。NumPy库的一个主要特征是它会给Python提供一个数组对象,其相比于标准的Python列表而言更适合用来做数学运算。下面通过示例查看标准的Python列表对象和NumPy库中的数组对象之间的使用差别:
x = [1, 2, 3, 4] y = [5, 6, 7, 8] print(f'{x} * 2 is: {x * 2}') # print(x + 10) print(f'{x} + {y} = {x + y}') import numpy as np ax = np.array([1, 2, 3, 4]) ay = np.array([5, 6, 7, 8]) print(f'{ax} * 2 = {ax * 2}') print(f'{ax} + 10 = {ax + 10}') print(f'{ax} + {ay} = {ax + ay}') print(f'{ax} * {ay} = {ax * ay}')
执行py文件,输出结果如下:
[1, 2, 3, 4] * 2 is: [1, 2, 3, 4, 1, 2, 3, 4] [1, 2, 3, 4] + [5, 6, 7, 8] = [1, 2, 3, 4, 5, 6, 7, 8] [1 2 3 4] * 2 = [2 4 6 8] [1 2 3 4] + 10 = [11 12 13 14] [1 2 3 4] + [5 6 7 8] = [6 8 10 12] [1 2 3 4] * [5 6 7 8] = [5 12 21 32]
两种方案中数组的基本数学运算结果并不相同。NumPy库中的标量运算(如ax*2或ax+10)会作用在每一个元素上。当两个操作数都是数组的时候,执行元素对等位置计算,并最终生成一个新的数组。
对整个数组中的所有元素同时执行数学运算,可以使得作用在整个数组上的函数运算简单又快速。计算多项式的值的示例如下:
def f(px): return 3 * px ** 2 - 2 * px + 7 print(f'f(ax) = {f(ax)}')
执行py文件,输出结果如下:
f(ax) = [ 8 15 28 47]
NumPy还为数组操作提供了大量的通用函数,这些函数可以作为math模块中类似函数的替代,示例如下:
print(f'np.sqrt({ax}) = {np.sqrt(ax)}') print(f'np.cos({ax}) = {np.cos(ax)}')
执行py文件,输出结果如下:
np.sqrt([1 2 3 4]) = [1. 1.41421356 1.73205081 2. ] np.cos([1 2 3 4]) = [0.54030231 -0.41614684 -0.9899925 -0.65364362]
使用这些通用函数要比循环数组并使用math模块中的函数执行计算快得多,因此应尽量选择NumPy的数组方案。
在底层实现中,NumPy数组使用了C或者Fortran语言的机制分配内存。也就是说,NumPy是一个非常大、连续并由同类型数据组成的内存区域,所以可以构造一个比普通Python列表大得多的数组。如构造一个10 000×10 000的浮点数二维网格,示例如下:
grid = np.zeros(shape=(10000,10000), dtype=float) print(f'np.zeros(shape=(10000,10000), dtype=float) result: \n{grid}')
执行py文件,输出结果如下:
np.zeros(shape=(10000,10000), dtype=float) result: [[0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.]]
所有的普通操作还是会同时作用在所有元素上:
grid += 10 print(f'grid + 10 result:\n{grid}') print(f'np.sin(grid) result:\n{np.sin(grid)}')
执行py文件,输出结果如下:
grid + 10 result: [[10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] ... [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.]] np.sin(grid) result: [[-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] ... [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111]]
对于NumPy数组,需要特别注意它扩展Python列表的索引功能——特别是对于多维数组。为了演示多维数组的索引功能,首先构造一个简单的二维数组:
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) print(f'a is:\n{a}') print(f'a[1] is:\n{a[1]}') print(f'a[:,1] is:\n{a[:,1]}') print(f'a[1:3, 1:3] is:\n{a[1:3, 1:3]}') a[1:3, 1:3] += 10 print(f'a is:\n{a}') print(f'a + [100, 101, 102, 103] is:\n{a + [100, 101, 102, 103]}') print(f'np.where(a < 10, a, 10) is:\n{np.where(a < 10, a, 10)}')
执行py文件,输出结果如下:
a is: [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]] a[1] is: [5 6 7 8] a[:,1] is: [ 2 6 10] a[1:3, 1:3] is: [[ 6 7] [10 11]] a is: [[ 1 2 3 4] [ 5 16 17 8] [ 9 20 21 12]] a + [100, 101, 102, 103] is: [[101 103 105 107] [105 117 119 111] [109 121 123 115]] np.where(a < 10, a, 10) is: [[ 1 2 3 4] [ 5 10 10 8] [ 9 10 10 10]]
NumPy是Python中很多科学与工程库的基础,同时也是被广泛使用的最大、最复杂的模块之一。NumPy通过一些简单程序能完成有趣的事情。
通常,导入NumPy库的时候,我们会使用语句import numpy as np。这样就不用在程序中一遍遍地输入numpy,只需要输入np即可,节省了输入时间。