接着上篇继续后面两个章节,数组变形和数组计算。
本节介绍四大类数组层面上的操作,具体有
重塑 (reshape) 和打平 (ravel, flatten) 这两个操作仅仅只改变数组的维度
用reshape()函数将一堆数组arr重塑成二维数组。
import numpy as np
arr = np.arange(12)
print(arr)
print(arr.reshape((4, 3)))
[ 0 1 2 3 4 5 6 7 8 9 10 11]
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
当你重塑高维矩阵时,不想花时间算某一维度的元素个数时,可以用「-1」取代,程序会自动帮你计算出来。比如把 12 个元素重塑成 (2, 6),你可以写成 (2,-1) 或者 (-1, 6)。
print(arr.reshape((2, -1)))
print(arr.reshape((-1, 4)))
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
用ravel()或flatten()函数将二维数组arr打平成一维数组
arr = np.arange(12).reshape((4,3))
print(arr)
ravel_arr = arr.ravel()
print(ravel_arr)
flatten_arr = arr.flatten()
print(flatten_arr)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[ 0 1 2 3 4 5 6 7 8 9 10 11]
[ 0 1 2 3 4 5 6 7 8 9 10 11]
问题:
思考:为什么重塑后的数组不是
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
思考:为什么打平后的数组不是
[ 0 3 6 9 1 4 7 10 2 5 8 11]
要回答本节两个问题,需要了解 numpy 数组在内存块的存储方式。
行主序 (row-major order) 指每行的元素在内存块中彼此相邻,而列主序 (column-major order) 指每列的元素在内存块中彼此相邻。
在众多计算机语言中,
在 numpy 数组中,默认的是 行主序,即 order =‘C’。现在可以回答本节那两个问题了。
如果你真的想在「重塑」和「打平」时用列主序,只用把 order 设为 ‘F’,以重塑举例:
print(arr.reshape((4,3), order='F'))
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
「打平」有两个函数 ravel() 或 flatten()?它们的区别在哪里?
函数 ravel() 或 flatten() 的不同之处是
用代码验证一下,首先看 flatten(),将打平后的数组 flatten 第一个元素更新为 10000,并没有对原数组 arr 产生任何影响 (证明 flatten() 是复制了原数组)
arr = np.arange(6).reshape(2,3)
print(arr)
flatten = arr.flatten()
print(flatten)
flatten[0] = 10000
print(arr)
[[0 1 2]
[3 4 5]]
[0 1 2 3 4 5]
[[0 1 2]
[3 4 5]]
再看 ravel() 在「列主序」打平,将打平后的数组 ravel_F 第一个元素更新为 10000,并没有对原数组 arr 产生任何影响 (证明 ravel(order=‘F’) 是 复制了 原数组)
ravel_F = arr.ravel(order='F')
print(ravel_F)
ravel_F[0] = 10000
print(ravel_F)
print(arr)
[0 3 1 4 2 5]
[10000 3 1 4 2 5]
[[0 1 2]
[3 4 5]]
最后看 ravel() 在「行主序」打平,将打平后的数组 ravel_C 第一个元素更新为 10000,原数组 arr[0][0] 也变成了 10000 (证明 ravel() 没有复制原数组)
ravel_C = arr.ravel(order='C')
ravel_C[0] = 10000
print(ravel_C)
print(arr)
[10000 1 2 3 4 5]
[[10000 1 2]
[ 3 4 5]]
合并 (concatenate, stack) 和分裂 (split) 这两个操作仅仅只改变数组的分合
使用「合并」函数有三种选择
用下面两个数组来举例:
arr1 = np.array([[1, 2, 3],[4, 5, 6]])
arr2 = np.array([[7, 8, 9], [10, 11, 12]])
np.concatenate([arr1, arr2], axis=0)
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
np.concatenate([arr1, arr2], axis=1)
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
在 concatenate() 函数里通过设定轴,来对数组进行竖直方向合并 (轴 0) 和水平方向合并 (轴 1)。
vstack, hstack, dstack
通用的东西是好,但是可能效率不高,NumPy 里还有专门合并的函数
vstack:v 代表 vertical,竖直合并,等价于 concatenate(axis=0)
hstack:h 代表 horizontal,水平合并,等价于 concatenate(axis=1)
dstack:d 代表 depth-wise,按深度合并,深度有点像彩色照片的 RGB 通道
一图胜千言:
用代码验证一下:
print( np.vstack((arr1, arr2)) )
print('- '*10)
print( np.hstack((arr1, arr2)) )
print('- '*10)
print( np.dstack((arr1, arr2)) )
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
- - - - - - - - - -
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
- - - - - - - - - -
[[[ 1 7]
[ 2 8]
[ 3 9]]
[[ 4 10]
[ 5 11]
[ 6 12]]]
和 vstack, hstack 不同,dstack 将原数组的维度增加了一维。
np.dstack((arr1, arr2)).shape
(2, 3, 2)
print( np.r_[arr1,arr2] )
print('- '*10)
print( np.c_[arr1,arr2] )
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
- - - - - - - - - -
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
除此之外,r_() 和 c_() 有什么特别之处么?(如果完全和 vstack() 和hstack() 一样,那也没有存在的必要了)
print(np.r_[-2:2:1, [0]*3, 5, 6])
[-2 -1 0 1 0 0 0 5 6]
np.r_['r', [1,2,3], [4,5,6]]
matrix([[1, 2, 3, 4, 5, 6]])
看不懂吧?没事,先用程序感受一下:
print( np.r_['0,2,0', [1,2,3], [4,5,6]] )
print( np.r_['0,2,1', [1,2,3], [4,5,6]] )
print( np.r_['1,2,0', [1,2,3], [4,5,6]] )
print( np.r_['1,2,1', [1,2,3], [4,5,6]] )
[[1]
[2]
[3]
[4]
[5]
[6]]
[[1 2 3]
[4 5 6]]
[[1 4]
[2 5]
[3 6]]
[[1 2 3 4 5 6]]
还看不懂吧 (但至少知道完事后的维度是 2,即字符串 ‘a,b,c’ 的 b 起的作用 )?没事,我再画个图。
还没懂彻底吧?没事,我再解释下。
字符串 ‘a,b,c’ 总共有四类,分别是
函数里两个数组 [1,2,3], [4,5,6] 都是一维
接下来如何合并就看 a 的值了
使用「分裂」函数有两种选择
用下面数组来举例:
arr = np.arange(25).reshape(5, -1)
arr
array([[ 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]])
first, second , third = np.split(arr, [1,3])
print( 'The first split is', first )
print( 'The second split is', second )
print( 'The third split is', third )
The first split is [[0 1 2 3 4]]
The second split is [[ 5 6 7 8 9]
[10 11 12 13 14]]
The third split is [[15 16 17 18 19]
[20 21 22 23 24]]
split() 默认沿着轴 0 分裂,其第二个参数 [1, 3] 相当于是个切片操作,将数组分成三部分:
第一部分 - :1 (即第 1 行)
第二部分 - 1:3 (即第 2 到 3 行)
第二部分 - 3: (即第 4 到 5 行)
hsplit, vsplit
vsplit() 和 split(axis=0) 等价,hsplit() 和 split(axis=1) 等价。一图胜千言:
为了和上面不重复,我们只看 hsplit。
first, second, third = np.hsplit(arr,[1,3])
print( 'The first split is', first )
print( 'The second split is', second )
print( 'The third split is', third )
The first split is [[ 0]
[ 5]
[10]
[15]
[20]]
The second split is [[ 1 2]
[ 6 7]
[11 12]
[16 17]
[21 22]]
The third split is [[ 3 4]
[ 8 9]
[13 14]
[18 19]
[23 24]]
重复 (repeat) 和拼接 (tile) 这两个操作本质都是复制
函数 repeat() 复制的是数组的每一个元素,参数有几种设定方法:
一维数组:用标量和列表来复制元素的个数
多维数组:用标量和列表来复制元素的个数,用轴来控制复制的行和列
标量
arr = np.arange(3)
print(arr)
print(arr.repeat(3))
[0 1 2]
[0 0 0 1 1 1 2 2 2]
标量参数 3 - 数组 arr 中每个元素复制 3 遍。
print(arr.repeat([2, 3, 4]))
[0 0 1 1 1 2 2 2 2]
列表参数 [2,3,4] - 数组 arr 中每个元素分别复制 2, 3, 4 遍。
arr2d = np.arange(6).reshape((2, -1))
print(arr2d)
print(arr2d.repeat(2, axis=0))
print(arr2d.repeat(2, axis=1))
[[0 1 2]
[3 4 5]]
[[0 1 2]
[0 1 2]
[3 4 5]
[3 4 5]]
[[0 0 1 1 2 2]
[3 3 4 4 5 5]]
标量参数 2 和轴 0 - 数组 arr2d 中每个元素沿着轴 0 复制 2 遍。
print( arr2d.repeat([2,3,4], axis=1) )
[[0 0 1 1 1 2 2 2 2]
[3 3 4 4 4 5 5 5 5]]
列表参数 [2,3,4] 和轴 1 - 数组 arr2d 中每个元素沿着轴 1 分别复制 2, 3, 4 遍。
函数 tile() 复制的是数组本身,参数有几种设定方法:
标量:把数组当成一个元素,一列一列复制
形状:把数组当成一个元素,按形状复制
标量
arr2d = np.arange(6).reshape((2,3))
print( arr2d )
print( np.tile(arr2d,2) )
[[0 1 2]
[3 4 5]]
[[0 1 2 0 1 2]
[3 4 5 3 4 5]]
标量参数 2 - 数组 arr 按列复制 2 遍。
print( np.tile(arr2d, (2,3)) )
[[0 1 2 0 1 2 0 1 2]
[3 4 5 3 4 5 3 4 5]
[0 1 2 0 1 2 0 1 2]
[3 4 5 3 4 5 3 4 5]]
标量参数 (2,3) - 数组 arr 按形状复制 6 (2×3) 遍,并以 (2,3) 的形式展现。
本节讨论数组的其他操作,包括排序 (sort),插入 (insert),删除 (delete) 和复制 (copy)。
排序包括直接排序 (direct sort) 和间接排序 (indirect sort)。
arr = np.array([5,3,2,6,1,4])
print( 'Before sorting', arr )
arr.sort()
print( 'After sorting', arr )
Before sorting [5 3 2 6 1 4]
After sorting [1 2 3 4 5 6]
sort()函数是按升序 (ascending order) 排列的,该函数里没有参数可以控制 order,因此你想要按降序排列的数组,只需
print( arr[::-1] )
[6 5 4 3 2 1]
用来排序 numpy 用两种方式:
arr.sort()
np.sort( arr )
第一种 sort 会改变 arr,第二种 sort 在排序时创建了 arr 的一个复制品,不会改变 arr。看下面代码,用一个形状是 (3, 4) 的「二维随机整数」数组来举例,用整数是为了便于读者好观察排序前后的变化:
arr = np.random.randint( 40, size=(3,4) )
arr
array([[ 6, 21, 6, 20],
[20, 16, 21, 0],
[25, 8, 33, 15]])
第一种 arr.sort(),对第一列排序,发现 arr 的元素 改变 了。
arr[:, 0].sort()
arr
array([[ 6, 21, 6, 20],
[20, 16, 21, 0],
[25, 8, 33, 15]])
第二种 np.sort(arr),对第二列排序,但是 arr 的元素不变。
np.sort(arr[:,1])
array([ 8, 16, 21])
print( arr )
[[ 6 21 6 20]
[20 16 21 0]
[25 8 33 15]]
此外也可以在不同的轴上排序,对于二维数组,在「轴 0」上排序是「跨行」排序,在「轴 1」上排序是「跨列」排序。
arr.sort(axis=1)
print( arr )
[[ 6 6 20 21]
[ 0 16 20 21]
[ 8 15 25 33]]
有时候我们不仅仅只想排序数组,还想在排序过程中 提取每个元素在原数组对应的索引(index) ,这时 argsort() 就派上用场了。以排列下面五个学生的数学分数为例:
score = np.array([100, 60, 99, 80, 91])
idx = score.argsort()
print( idx )
[1 3 4 2 0]
用这个 idx 对 score 做一个「花式索引」得到 (还记得上贴的内容吗?)
print( score[idx] )
[ 60 80 91 99 100]
再看一个二维数组的例子。
arr = np.random.randint( 40, size=(3,4) )
print( arr )
[[ 1 19 7 30]
[ 9 8 1 12]
[16 31 10 0]]
对其第一行 arr[0] 排序,获取索引,在应用到所用行上
arr[:, arr[0].argsort()]
array([[ 1, 7, 19, 30],
[ 9, 1, 8, 12],
[16, 10, 31, 0]])
这不就是「花式索引」吗?来我们分解一下以上代码,先看看索引。
print( arr[0].argsort() )
[0 2 1 3]
「花式索引」来了,结果和上面一样的。
arr[:, [2, 0, 3, 1]]
array([[ 7, 1, 30, 19],
[ 1, 9, 12, 8],
[10, 16, 0, 31]])
和列表一样,我们可以给 numpy 数组
a = np.arange(6)
print( a )
print( np.insert(a, 1, 100) )
print( np.delete(a, [1,3]) )
[0 1 2 3 4 5]
[ 0 100 1 2 3 4 5]
[0 2 4 5]
用copy()函数来复制数组 a 得到 a_copy,很明显,改变 a_copy 里面的元素不会改变 a。
a = np.arange(6)
a_copy = a.copy()
print( 'Before changing value, a is', a )
print( 'Before changing value, a_copy is', a_copy )
a_copy[-1] = 99
print( 'After changing value, a_copy is', a_copy )
print( 'After changing value, a is', a )
Before changing value, a is [0 1 2 3 4 5]
Before changing value, a_copy is [0 1 2 3 4 5]
After changing value, a_copy is [ 0 1 2 3 4 99]
After changing value, a is [0 1 2 3 4 5]
节介绍四大类数组计算,具体有
Numpy 数组元素层面计算包括:
先定义两个数组 arr1 和 arr2。
arr1 = np.array([[1., 2., 3.], [4., 5., 6.]])
arr2 = np.ones((2,3)) * 2
print( arr1 )
print( arr2 )
[[1. 2. 3.]
[4. 5. 6.]]
[[2. 2. 2.]
[2. 2. 2.]]
print( arr1 + arr2 + 1 )
print( arr1 - arr2 )
print( arr1 * arr2 )
print( arr1 / arr2 )
[[4. 5. 6.]
[7. 8. 9.]]
[[-1. 0. 1.]
[ 2. 3. 4.]]
[[ 2. 4. 6.]
[ 8. 10. 12.]]
[[0.5 1. 1.5]
[2. 2.5 3. ]]
print( 1 / arr1 )
print( arr1 ** 2 )
print( np.exp(arr1) )
print( np.log(arr1) )
[[1. 0.5 0.33333333]
[0.25 0.2 0.16666667]]
[[ 1. 4. 9.]
[16. 25. 36.]]
[[ 2.71828183 7.3890561 20.08553692]
[ 54.59815003 148.4131591 403.42879349]]
[[0. 0.69314718 1.09861229]
[1.38629436 1.60943791 1.79175947]]
arr1 > arr2
arr1 > 3
array([[False, False, False],
[ True, True, True]])
从上面结果可知
但是在「数组和标量间的比较」时,python 好像先把 3 复制了和 arr1 形状一样的数组 [[3,3,3], [3,3,3]],然后再在元素层面上作比较。上述这个复制标量的操作叫做「广播机制」,是 NumPy 里最重要的一个特点,在下一节会详细讲到。
在机器学习、金融工程和量化投资的编程过程中,因为运行速度的要求,通常会向量化 (vectorization) 而涉及大量的线性代数运算,尤其是矩阵之间的乘积运算。
但是,在 NumPy 默认不采用矩阵运算,而是数组 (ndarray) 运算。矩阵只是二维,而数组可以是任何维度,因此数组运算更通用些。
如果你非要二维数组 arr2d 进项矩阵运算,那么可以通过调用以下函数来实现:
创建数组 arr2d 和矩阵 A,注意它们的输出有 array 和 matrix 的关键词。
arr2d = np.array([[1,2],[3,1]])
arr2d
array([[1, 2],
[3, 1]])
A = np.asmatrix(arr2d)
A
matrix([[1, 2],
[3, 1]])
数组用 arr2d.T 操作或 arr.tranpose() 函数,而矩阵用 A.T 操作。主要原因就是 .T 只适合二维数据,上贴最后也举了个三维数组在轴 1 和轴 2 之间的转置,这时就需要用函数 arr2d.tranpose(1, 0, 2) 来实现了。
print( arr2d.T )
print( arr2d.transpose() )
print( A.T )
[[1 3]
[2 1]]
[[1 3]
[2 1]]
[[1 3]
[2 1]]
数组用 np.linalg.inv() 函数,而矩阵用 A.I 和 A**-1 操作。
print( np.linalg.inv(arr2d) )
print( A.I )
print( A**-1 )
[[-0.2 0.4]
[ 0.6 -0.2]]
[[-0.2 0.4]
[ 0.6 -0.2]]
[[-0.2 0.4]
[ 0.6 -0.2]]
相乘是个很模棱两可的概念
看个例子,「二维数组」相乘「一维数组」,「矩阵」相乘「向量」,看看有什么有趣的结果。
首先定义「一维数组」arr 和 「列向量」b:
arr = np.array([1,2])
b = np.asmatrix(arr).T
print( arr.shape, b.shape )
(2,) (2, 1)
由上面结果看出, arr 的形状是 (2,),只含一个元素的元组只说明 arr 是一维,数组是不分行数组或列数组的。而 b 的形状是 (2,1),显然是列向量。
相乘都是用 * 符号,
print( arr2d*arr )
print( A*b )
[[1 4]
[3 2]]
[[5]
[5]]
由上面结果可知,
二维数组相乘一维数组得到的还是个二维数组,解释它需要用到「广播机制」,这是下节的重点讨论内容。现在大概知道一维数组 [1 2] 第一个元素 1 乘上 [1 3] 得到 [1 3],而第二个元素 2 乘上 [2 1] 得到 [4 2]。
而矩阵相乘向量的结果和我们学了很多年的线代结果很吻合。
再看一个例子,「二维数组」相乘「二维数组」,「矩阵」相乘「矩阵」
print( arr2d*arr2d )
print( A*A )
[[1 4]
[9 1]]
[[7 4]
[6 7]]
由上面结果可知,
虽然两个二维数组相乘得到二维数组,但不是根据数学上矩阵相乘的规则得来的,而且由元素层面相乘得到的。两个 [[1 2], [3,1]] 的元素相乘确实等于 [[1 4], [9,1]]。
而矩阵相乘矩阵的结果和我们学了很多年的线代结果很吻合。
问题来了,那么怎么才能在数组上实现「矩阵相乘向量」和「矩阵相乘矩阵」呢?用点乘函数 dot()。
print( np.dot(arr2d,arr) )
print( np.dot(arr2d,arr2d) )
[5 5]
[[7 4]
[6 7]]
结果对了,但还有一个小小的差异
矩阵相乘列向量的结果是个列向量,写成 [[5],[5]],形状是 (2,1)
二维数组点乘一维数组结果是个一维数组,写成 [5, 5],形状是 (2,)
由此我们来分析下 NumPy 里的 dot() 函数,计算数组和数组之间的点乘结果。
本节的内容也来自〖张量 101〗,通常我们也把 n 维数组称为张量,点乘左右两边最常见的数组就是
分别看看三个简单例子。
例一:np.dot(向量, 向量) 实际上做的就是内积,即把两个向量每个元素相乘,最后再加总。点乘结果 10 是个标量 (0D 数组),形状 = ()。
x = np.array( [1, 2, 3] )
y = np.array( [3, 2, 1] )
z = np.dot(x,y)
print( z.shape )
print( z )
()
10
例二:np.dot(矩阵, 向量) 实际上做的就是普通的矩阵乘以向量。点乘结果是个向量 (1D 数组),形状 = (2, )。
x = np.array( [1, 2, 3] )
y = np.array( [[3, 2, 1], [1, 1, 1]] )
z = np.dot(y,x)
print( z.shape )
print( z )
(2,)
[10 6]
例三:np.dot(矩阵, 矩阵) 实际上做的就是普通的矩阵乘以矩阵。点乘结果是个矩阵 (2D 数组),形状 = (2, 3)。
x = np.array( [[1, 2, 3], [1, 2, 3], [1, 2, 3]] )
y = np.array( [[3, 2, 1], [1, 1, 1]] )
z = np.dot(y,x)
print( z.shape )
print( z )
(2, 3)
[[ 6 12 18]
[ 3 6 9]]
上面例子都是低维数组 (维度 ≤ 2) 的点乘运算,接下来我们看两个稍微复杂的例子。
例四:当 x 是 3D 数组,y 是 1D 数组,np.dot(x, y) 是将 x 和 y 最后一维的元素相乘并加总。此例 x 的形状是 (2, 3, 4),y 的形状是 (4, ),因此点乘结果的形状是 (2, 3)。
x = np.ones( shape=(2, 3, 4) )
y = np.array( [1, 2, 3, 4] )
z = np.dot(x,y)
print( z.shape )
print( z )
(2, 3)
[[10. 10. 10.]
[10. 10. 10.]]
例五:当 x 是 3D 数组,y 是 2D 数组,np.dot(x, y) 是将 x 的最后一维和 y 的倒数第二维的元素相乘并加总。此例 x 的形状是 (2, 3, 4),y 的形状是 (4, 2),因此点乘结果的形状是 (2, 3, 2)。
x = np.random.normal( 0, 1, size=(2, 3, 4) )
y = np.random.normal( 0, 1, size=(4, 2) )
z = np.dot(x,y)
print( z.shape )
print( z )
(2, 3, 2)
[[[-0.30196459 3.12638438]
[-2.43813972 2.12859614]
[-1.56540189 2.08110203]]
[[ 0.93970812 -0.94231179]
[-2.16935379 -0.07067503]
[-1.95390613 1.90075627]]]
例五的规则也适用于 nD 数组和 mD 数组 (当 m ≥ 2 时) 的点乘。
在数组中,元素可以以不同方式整合 (aggregation)。拿求和 (sum) 函数来说,我们可以对数组
先定义数组
arr = np.arange(1,7).reshape((2,-1))
arr
array([[1, 2, 3],
[4, 5, 6]])
不难看出它是一个矩阵,分别对全部元素、跨行 (across rows)、跨列 (across columns) 求和:
print( 'The total sum is', arr.sum() )
print( 'The sum across rows is', arr.sum(axis=0) )
print( 'The sum across columns is', arr.sum(axis=1) )
The total sum is 21
The sum across rows is [5 7 9]
The sum across columns is [ 6 15]
分析上述结果:
行和列这些概念对矩阵 (二维矩阵) 才适用,高维矩阵还是要用轴 (axis) 来区分每个维度。让我们抛弃「行列」这些特殊概念,拥抱「轴」这个通用概念来重看数组 (一到四维) 吧。
规律:n 维数组就有 n 层方括号。最外层方括号代表「轴 0」即 axis=0,依次往里方括号对应的 axis 的计数加 1。
严格来说,numpy 打印出来的数组可以想象带有多层方括号的一行数字。比如二维矩阵可想象成
[[1, 2, 3],[4, 5, 6]]
三维矩阵可想象成
[[[1,2,3], [4,5,6]], [[7,8,9], [10,11,12]]]
由于屏幕的宽度不够,我们才把它们写成一列列的,如下
[ [ [1, 2, 3]
[4, 5, 6] ]
[ [7, 8, 9]
[10, 11, 12] ] ]
但在你脑海里,应该把它们想成一整行。这样会便于你理解如何按不同轴做整合运算。
有了轴的概念,我们再来看看 sum() 求和函数。
分析结果:
用代码验证一下:
arr = np.array([1,2,3])
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is', arr.sum(axis=0) )
The total sum is 6
The sum on axis0 is 6
二维数组x
分析结果:
1 到 6 的总和是 6
轴 0 上的元素 (被一个红方括号[]包住的) 是[1, 2, 3]和[4, 5, 6],求和得到[[5, 6, 7]]
轴 1 上的元素 (被两个蓝方括号[] 包住的) 分别是 1, 2, 3 和 4, 5, 6,求和得到 [[1+2+3, 4+5+6]]= [[6, 15]]
用代码验证一下:
arr = np.arange(1,7).reshape((2,3))
print( arr )
[[1 2 3]
[4 5 6]]
print( ‘The total sum is’, arr.sum() )
print( ‘The sum on axis0 is’, arr.sum(axis=0) )
print( ‘The sum on axis1 is’, arr.sum(axis=1) )
结果是对的,但是好像括号比上图推导出来的少一个。原因np.sum()里面有个参数是 keepdims,意思是「保留维度」,默认值时 False,因此会去除多余的括号,比如 [[5, 7, 9]] 会变成 [5, 7, 9]。
如果把 keepdims 设置为 True,那么打印出来的结果和上图推导的一模一样。
print( arr.sum(axis=0, keepdims=True) )
print( arr.sum(axis=1, keepdims=True) )
[[5 7 9]]
[[ 6]
[15]]
分析结果:
用代码验证一下:
arr = np.arange(1,13).reshape((2,2,3))
print(arr)
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is', arr.sum(axis=0) )
print( 'The sum on axis1 is', arr.sum(axis=1) )
print( 'The sum on axis2 is', arr.sum(axis=2) )
The total sum is 78
The sum on axis0 is [[ 8 10 12]
[14 16 18]]
The sum on axis1 is [[ 5 7 9]
[17 19 21]]
The sum on axis2 is [[ 6 15]
[24 33]]
打印出来的结果比上图推导结果少一个括号,也是因为 keepdims 默认为 False。
不解释了,彩色括号画的人要抓狂了。通用规律:当在某根轴上求和,明晰该轴的元素,再求和。具体说来:
用代码验证一下:
arr = np.arange(1,25).reshape((2,2,2,3))
print(arr)
[[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
[[[13 14 15]
[16 17 18]]
[[19 20 21]
[22 23 24]]]]
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is', arr.sum(axis=0) )
print( 'The sum on axis1 is', arr.sum(axis=1) )
print( 'The sum on axis2 is', arr.sum(axis=2) )
print( 'The sum on axis3 is', arr.sum(axis=3) )
The total sum is 300
The sum on axis0 is [[[14 16 18]
[20 22 24]]
[[26 28 30]
[32 34 36]]]
The sum on axis1 is [[[ 8 10 12]
[14 16 18]]
[[32 34 36]
[38 40 42]]]
The sum on axis2 is [[[ 5 7 9]
[17 19 21]]
[[29 31 33]
[41 43 45]]]
The sum on axis3 is [[[ 6 15]
[24 33]]
[[42 51]
[60 69]]]
打印出来的结果比上图推导结果少一个括号,也是因为 keepdims 默认为 False。
除了 sum 函数,整合函数还包括 min, max, mean, std 和 cumsum,分别是求最小值、最大值、均值、标准差和累加,这些函数对数组里的元素整合方式和 sum 函数相同,就不多讲了。总结来说我们可以对数组
整合函数= {sum, min, max, mean, std, cumsum}
当对两个形状不同的数组按元素操作时,可能会触发「广播机制」。具体做法,先适当复制元素使得这两个数组形状相同后再按元素操作,两个步骤:
在给出「广播机制」需要的严谨规则之前,我们先来看看几个简单例子。
arr = np.arange(5)
print( arr )
print( arr + 2 )
[0 1 2 3 4]
[2 3 4 5 6]
元素 2 被广播到数组 arr 的所有元素上。
arr = np.arange(12).reshape((4,3))
print( arr )
print( arr.mean(axis=0) )
print( arr - arr.mean(axis=0) )
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[4.5 5.5 6.5]
[[-4.5 -4.5 -4.5]
[-1.5 -1.5 -1.5]
[ 1.5 1.5 1.5]
[ 4.5 4.5 4.5]]
沿轴 0 的均值的一维数组被广播到数组 arr 的所有的行上。
现在我们来看看「广播机制」的规则:
当我们对两个数组操作时,如果它们的形状
因此,进行广播机制分两步
检查两个数组形状是否兼容,即从两个形状元组最后一个元素,来检查
它们是否相等
是否有一个等于 1
一旦它们形状兼容,确定两个数组的最终形状。
用个例子来应用以上广播机制规则
a = np.array([[1,2,3]])
b = np.array([[4],[5],[6]])
print( 'The shape of a is', a.shape )
print( 'The shape of b is', b.shape )
The shape of a is (1, 3)
The shape of b is (3, 1)
回顾进行广播机制的两步
检查数组 a 和 b 形状是否兼容,从两个形状元组 (1, 3) 和 (3, 1)最后一个元素开始检查,发现它们都满足『有一个等于 1』的条件。
因此它们形状兼容,两个数组的最终形状为 (max(1,3), max(3,1)) = (3, 3)
到此,a 和 b 被扩展成 (3, 3) 的数组,让我们看看 a + b 等于多少
c = a + b
print( 'The shape of c is', c.shape )
print( 'a is', a )
print( 'b is', b )
print( 'c = a + b =', c )
The shape of c is (3, 3)
a is [[1 2 3]]
b is [[4]
[5]
[6]]
c = a + b = [[5 6 7]
[6 7 8]
[7 8 9]]
a = np.arange(5)
b = np.array(2)
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b is', b.ndim, 'and the shape of b is', b.shape )
The dimension of a is 1 and the shape of a is (5,)
The dimension of b is 0 and the shape of b is ()
数组 a 和 b 形状分别为 (5,) 和 (),首先我们把缺失的维度用 1 补齐得到 (5,) 和 (1,),再根据广播机制那套流程得到这两个形状是兼容的,而且最终形状为 (5,)。
用代码来看看 a + b 等于多少
c = a + b
print( 'The dimension of c is', c.ndim, 'and the shape of c is', c.shape, '\n' )
print( 'a is', a )
print( 'b is', b )
print( 'c = a + b =', c )
The dimension of c is 1 and the shape of c is (5,)
a is [0 1 2 3 4]
b is 2
c = a + b = [2 3 4 5 6]
现在对广播机制有概念了吧,来趁热打铁搞清楚下面这五个例子,你就完全弄懂它了。
a = np.array( [[[1,2,3], [4,5,6]]] )
b1 = np.array( [[1,1,1], [2,2,2], [3,3,3]] )
b2 = np.arange(3).reshape((1,3))
b3 = np.arange(6).reshape((2,3))
b4 = np.arange(12).reshape((2,2,3))
b5 = np.arange(6).reshape((2,1,3))
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b1 is', b.ndim, 'and the shape of b1 is', b1.shape, '\n')
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b2 is', b.ndim, 'and the shape of b2 is', b2.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b3 is', b.ndim, 'and the shape of b3 is', b3.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b4 is', b.ndim, 'and the shape of b4 is', b4.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b5 is', b.ndim, 'and the shape of b5 is', b5.shape )
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b1 is 0 and the shape of b1 is (3, 3)
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b2 is 0 and the shape of b2 is (1, 3)
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b3 is 0 and the shape of b3 is (2, 3)
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b4 is 0 and the shape of b4 is (2, 2, 3)
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b5 is 0 and the shape of b5 is (2, 1, 3)
对于数组 a 和 b1,它们形状是 (1, 2, 3) 和 (3, 3)。元组最后一个都是 3,兼容;倒数第二个是 3 和 2,即不相等,也没有一个是 1,不兼容!a 和 b1 不能进行广播机制。不行就看看下面代码:
c1 = a + b1
print( c1 )
print( c1.shape )
-------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in ()
----> 1 c1 = a + b1
2 print( c1 )
3 print( c1.shape )
ValueError: operands could not be broadcast together with shapes (1,2,3) (3,3)
a 和其他 b2, b3, b4, b5 都可以进行广播机制
c2 = a + b2
print( c2 )
print( c2.shape )
[[[1 3 5]
[4 6 8]]]
(1, 2, 3)
c3 = a + b3
print( c3 )
print( c3.shape )
[[[ 1 3 5]
[ 7 9 11]]]
(1, 2, 3)
c4 = a + b4
print( c4 )
print( c4.shape )
[[[ 1 3 5]
[ 7 9 11]]
[[ 7 9 11]
[13 15 17]]]
(2, 2, 3)
c5 = a + b5
print( c5 )
print( c5.shape )
[[[ 1 3 5]
[ 4 6 8]]
[[ 4 6 8]
[ 7 9 11]]]
(2, 2, 3)
NumPy 篇终于完结!即上贴讨论过的数组创建、数组存载和数组获取,本贴讨论了数组变形、数组计算。
数组变形有以下重要操作:
数组计算有以下重要操作:
下篇讨论用于科学计算的 SciPy