目录
前提:导入numpy库。
import numpy as np
一、数组的创建
1.1 创建数组
创建numpy数组的三种方式:
- 按步就班法:np.array() 用在列表和元组上
- 定隔定点法:np.arange() 和 np.linspace()
- 一步登天法:np.ones(),np.zeros(),np.eye() 和 np.random.random()
1. 按步就班法
list = [1, 2, 3.5, 5.5, 8]
np.array(list)
array([1. , 2. , 3.5, 5.5, 8. ])
tuple = (1, 2, 3.5, 5.5, 8)
print(np.array(tuple))
np.array(tuple)
[1. 2. 3.5 5.5 8. ]
array([1. , 2. , 3.5, 5.5, 8. ])
注意,numpy数组的输出都带有 array() 的字样,里面的元素用“中括号 []”框住。用函数 print 打印 numpy 数组就没有 array() 的字样了。
2. 定隔定点法
- 定隔的 arange(): 固定元素大小间隔
- 定点的 linspace():固定元素个数
函数 arange 的参数为起点,终点,间隔:arange(start , stop , step)。其中 stop 必须要有,start 和 step 没有的话默认为 1。
print(np.arange(10))
print(np.arange(2, 10))
print(np.arange(2, 10, 2))
[0 1 2 3 4 5 6 7 8 9]
[2 3 4 5 6 7 8 9]
[2 4 6 8]
函数 linspace 的参数为起点,终点,点数:linspace (start , stop , num)。其中 start 和 stop 必须要有,num 没有的话默认为 50。
print(np.linspace(2, 6, 3))
print(np.linspace(2, 8, 4))
print(np.linspace(0, 49))
[2. 4. 6.]
[2. 4. 6. 8.]
[ 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. 32. 33. 34. 35.
36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.]
3. 一步登天法
- zeros(): 创建全是 0 的 n 维数组
- ones(): 创建全是 1 的 n 维数组
- random():创建随机 n 维数组
- eye(): 创建对角矩阵
前三种,由于输出是 n 为数组,它们的参数是一个「标量」或「元组类型的形状」。
print(np.zeros(5)) # 标量5代表形状(5,)
print(np.ones((2, 3)))
print(np.random.random((2, 3, 4)))
[0. 0. 0. 0. 0.]
[[1. 1. 1.]
[1. 1. 1.]]
[[[0.42180299 0.84854733 0.115058 0.65318331]
[0.03357884 0.05289622 0.19615066 0.22481889]
[0.05550249 0.2390295 0.24496322 0.83995308]]
[[0.73575253 0.19963972 0.3201794 0.75923466]
[0.54934324 0.6029062 0.39607958 0.9521938 ]
[0.10393022 0.71187835 0.04202606 0.85566354]]]
函数 eye(),它的参数就是一个标量,控制矩阵的行数或列数。
eye() 里面的参数 k
- k = 0: 默认设置,代表 1 落在对角线上
- k = 1: 代表 1 落在对角线右上方
- k = -1:代表 1 落在对角线左下方
print(np.eye(4))
print(np.eye(4, k=1))
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
[[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]]
1.2 数组的性质
先用按部就班的 np.array() 带列表生成二维数组 arr。
arr = np.array([1, 2, 3.5, 5.5, 8])
再用 dir(arr) 来查看数组的属性,之后我们对 type,ndim,len(),size,shape,stride,dtype 这几个常用函数分析一波。
- type: 数组类型,当然是 numpy.ndarray
- ndim: 数组维度个数
- len(): 数组长度
- size: 数组元素个数
- shape: 数组形状,即每个维度的元素个数 (用元组来表示),只有一维,元素个数为 5,写成元组形式是 (5,)
- strides:数组跨度,即在某一维度下为了获取到下一个元素需要「跨过」的字节数 (用元组来表示),float64 是 8 个字节数 (bytes),因此跨度为 8
- dtype: 数组元素类型 (注意和 type 区分)
print('类型:', type(arr))
print('维度:', arr.ndim)
print('长度:', len(arr))
print('个数:', arr.size)
print('形状:', arr.shape)
print('跨度:', arr.strides)
print('类型:', arr.dtype)
类型: <class 'numpy.ndarray'>
维度:
长度: 5
个数: 5
形状: (5,)
跨度: (8,)
类型: float64
二维数组,甚至多维数组,大家可自行测试体会。
二、数组的存载
2.1 numpy 自身的 .npy 格式
- save:np.save( ‘’文件名”,数组 ) 即可保存为 .npy 格式
- load:np.load( "文件名" ) 即可加载该文件
arr_disk = np.arange(8)
np.save("arr_disk", arr_disk) # 保存在当前目录下
np.load("arr_disk.npy")
array([0, 1, 2, 3, 4, 5, 6, 7])
2.2 文本 .txt 格式
- savetxt:用 np.savetxt( ‘’文件名”,数组 ) 即可保存为 .txt 格式
- loadtxt: 用 np.loadtxt( "文件名" ) 即可加载该文件
arr_text = np.array([[1., 2., 3.], [4., 5., 6.]])
np.savetxt("arr_text.txt", arr_text) # 保存为当前目录下
np.loadtxt("arr_text.txt")
array([[1., 2., 3.],
[4., 5., 6.]])
2.3 文本 .csv 格式
arr_csv = np.array([[1, 2, 3], [4, 5, 6]])
np.savetxt("arr_csv.csv", arr_csv) # 保存为当前目录下
np.loadtxt("arr_csv.csv")
# 或 np.genfromtxt("arr_csv.csv")
array([[1., 2., 3.],
[4., 5., 6.]])
三、数组的获取
获取数组是通过索引 (indexing) 和切片 (slicing) 来完成的。
- 索引:获取一个特定位置的元素,写法是 arr[index]
- 切片:获取一段特定位置的元素,写法是 arr[start : stop : step]
切片的操作是可以用索引操作来实现的 (一个一个总能凑成一段),只是没必要罢了。为了简化,我们在本章三节标题里把切片和索引都叫做索引。索引数组有三种形式,正规索引 (normal indexing)、布尔索引 (boolean indexing) 和花式索引 (fancy indexing)。
3.1 正规索引
一维数组:
arr1 = np.arange(10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(arr1[6]) # 索引
print(arr1[1:5]) # 切片
6
[1 2 3 4]
二维数组:
arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2)
print(arr2[2]) # 索引第三行
print(arr2[0][2]) # 索引第一行第三列
print(arr2[0, 2]) # 索引第一行第三列
[[1 2 3]
[4 5 6]
[7 8 9]]
[7 8 9]
3
3
print(arr2[:2]) # 切片前两行
print(arr2[:, [0, 2]]) # 切片第一列和第三列
print(arr2[1, :2]) # 切片第二行的前两个元素
print(arr2[:2, 2]) # 切片第三列的前两个元素
[[1 2 3]
[4 5 6]]
[[1 3]
[4 6]
[7 9]]
[4 5]
[3 6]
3.2 布尔索引
布尔索引,就是用一个由布尔 (boolean) 类型值组成的数组来选择元素的方法。
code = np.array(['BABA', 'FB', 'JD', 'BABA', 'JD', 'FB'])
price = np.array([[170,177,169], [150,159,153],
[24,27,26], [165,170,167],
[22,23,20], [155,116,157]])
print(code == 'BABA')
print(price[code == 'BABA'])
print(price[code == 'BABA', :1])
注:这种布尔索引的操作在 Pandas 更常用也更方便。
3.3 花式索引
花式索引是获取数组中想要的特定元素的有效方法。
arr = np.arange(32).reshape(8,4)
print(arr)
[[ 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]]
print(arr[[4,3,6]], '\n') # 按特定顺序来获取第 5,4 和 7 行
print(arr[[-4,-3,-6]], '\n') # 按特定顺序来获取倒数第 4,3 和 6 行
print(arr[[1,5,7,2], [0,3,1,2]]) # 获取第二行第一列、第六行第四列、第八行第二列、第三行第三列的元素
# print(np.array([arr[1,0], arr[5,3], arr[7,1], arr[2,2]]))
[[16 17 18 19]
[12 13 14 15]
[24 25 26 27]]
[[16 17 18 19]
[20 21 22 23]
[ 8 9 10 11]]
[ 4 23 29 10]
print(arr[:,[0,3,1,2]]) # 把原先的 [0,1,2,3] 的列换成 [0,3,1,2]
[[ 0 3 1 2]
[ 4 7 5 6]
[ 8 11 9 10]
[12 15 13 14]
[16 19 17 18]
[20 23 21 22]
[24 27 25 26]
[28 31 29 30]]
四、数组的变形
本节介绍四大类数组层面上的操作,具体有:
- 重塑 (reshape) 和打平 (ravel, flatten)
- 合并 (concatenate, stack) 和分裂 (split)
- 重复 (repeat) 和拼接 (tile)
- 其他操作 (sort, insert, delete, copy)
4.1 重塑和打平
重塑 (reshape) 和打平 (ravel, flatten) 这两个操作仅仅只改变数组的维度:
- 重塑:是从低维到高维
- 打平:是从高维到低维
1. 重塑
用reshape()函数将一维数组 arr 重塑成二维数组。
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, 6)))
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
注意:
在众多计算机语言中,
-
默认行主序的有 C 语言( order=‘C’ 等价于行主序)
-
默认列主序的有 Fortran 语言( order=‘F’ 等价于列主序)
在 numpy 数组中,默认的是行主序,即 order ='C'。如果你真的想在「重塑」和「打平」时用列主序,只用把 order 设为 'F',以重塑举例:
arr = np.arange(12)
print(arr.reshape((4, 3)))
print(arr.reshape((4,3), order='F'))
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
2. 打平
用 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]
函数 ravel() 或 flatten() 的不同之处是:
- ravel() 按「行主序」打平时没有复制原数组,按「列主序」在打平时复制了原数组
- flatten() 在打平时复制了原数组
4.2 合并和分裂
合并 (concatenate, stack) 和分裂 (split) 这两个操作仅仅只改变数组的分合:
- 合并:是多合一
- 分裂:是一分多
1. 合并
使用「合并」函数有三种选择:
- 有通用的 concatenate
- 有专门的 vstack, hstack, dstack
- 有极简的 r_, c_
用下面两个数组来举例:
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[7, 8, 9], [10, 11, 12]])
在 concatenate() 函数里通过设定轴,来对数组进行竖直方向合并 (轴 0) 和水平方向合并 (轴 1)。
print(np.concatenate([arr1, arr2], axis=0))
print(np.concatenate([arr1, arr2], axis=1))
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
在 NumPy 里还有专门合并的函数
- vstack:v 代表 vertical,竖直合并,等价于 concatenate(axis=0)
- hstack:h 代表 horizontal,水平合并,等价于 concatenate(axis=1)
- dstack:d 代表 depth-wise,按深度合并,深度有点像彩色照片的 RGB 通道
print(np.vstack((arr1, arr2)))
print(np.hstack((arr1, arr2)))
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)
此外,还有一种更简单的在竖直和水平方向合并的函数,r_() 和 c_()。
print(np.r_[arr1,arr2])
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]]
2. 分裂
使用「分裂」函数有两种选择
- 有通用的 split
- 有专门的 hsplit, vsplit
用下面数组来举例:
arr = np.arange(25).reshape((5,5))
print(arr)
[[ 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]]
和 concatenate() 函数一样,我们可以在 split() 函数里通过设定轴,来对数组沿着竖直方向分裂 (轴 0) 和沿着水平方向分裂 (轴 1)。
first, second, third = np.split(arr, [1,3])
print('The first split is', first) # 第 1 行
print('The second split is', second) # 第 2 到 3 行
print('The third split is', third) # 第 4 到 5 行
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] 相当于是个切片操作,将数组分成三部分。
vsplit() 和 split(axis=0) 等价,hsplit() 和 split(axis=1) 等价。
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]]
4.3 重复和拼接
重复 (repeat) 和拼接 (tile) 这两个操作本质都是复制。
- 重复:是在元素层面复制
- 拼接:是在数组层面复制
1. 重复
函数 repeat() 复制的是数组的每一个元素,参数有几种设定方法:
- 一维数组:用标量和列表来复制元素的个数
- 多维数组:用标量和列表来复制元素的个数,用轴来控制复制的行和列
arr = np.arange(5)
print(arr)
print(arr.repeat(3)) # 数组 arr 中每个元素复制 3 遍
print(arr.repeat([1, 2, 3, 4, 5])) # 数组 arr 中每个元素分别复制 1, 2, 3, 4, 5 遍
[0 1 2 3 4]
[0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
[0 1 1 2 2 2 3 3 3 3 4 4 4 4 4]
arr2 = np.arange(6).reshape((2,3))
print(arr2)
print(arr2.repeat(2, axis=0)) # 数组 arr2 中每个元素沿着轴 0 复制 2 遍
print(arr2.repeat([2,3,4], axis=1)) # 数组 arr2 中每个元素沿着轴 1 分别复制 2, 3, 4 遍
[[0 1 2]
[3 4 5]]
[[0 1 2]
[0 1 2]
[3 4 5]
[3 4 5]]
[[0 0 1 1 1 2 2 2 2]
[3 3 4 4 4 5 5 5 5]]
2. 拼接
函数 tile() 复制的是数组本身,参数有几种设定方法:
- 标量:把数组当成一个元素,一列一列复制
- 形状:把数组当成一个元素,按形状复制
arr2 = np.arange(6).reshape((2,3))
print(arr2)
print(np.tile(arr2, 2)) # 数组 arr2 按列复制 2 遍
print(np.tile(arr2, (2, 3))) # 数组 arr2 按形状复制 6 (2×3) 遍,并以 (2,3) 的形式展现
[[0 1 2]
[3 4 5]]
[[0 1 2 0 1 2]
[3 4 5 3 4 5]]
[[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]]
4.4 其他操作
本节讨论数组的其他操作,包括排序 (sort),插入 (insert),删除 (delete) 和复制 (copy)。
1. 排序
排序包括直接排序 (direct sort) 和间接排序 (indirect sort)。
直接排序
arr = np.array([5, 3, 2, 6, 1, 4])
arr.sort()
print(arr)
[1 2 3 4 5 6]
sort()函数是按升序 (ascending order) 排列的,该函数里没有参数可以控制 order,因此你想要按降序排列的数组,只需:
print(arr[::-1])
[6 5 4 3 2 1]
注意:
用来排序 numpy 用两种方式:
- arr.sort():sort 会改变 arr
- np.sort(arr):sort 在排序时创建了 arr 的一个复制品,不会改变 arr
间接排序
有时候我们不仅仅只想排序数组,还想在排序过程中提取每个元素在原数组对应的索引(index),这时 argsort() 就派上用场了。
score = np.array([100, 60, 99, 80, 91])
idx = score.argsort()
print(idx) # 打印数据由小到大的地址
print(score[idx])
[1 3 4 2 0]
[ 60 80 91 99 100]
arr = np.random.randint(40, size=(3,4))
print(arr)
print(arr[:, arr[0].argsort()]) # 对其第一行 arr[0] 排序,获取索引,在应用到所用行上
[[24 16 39 34]
[10 5 29 35]
[39 22 17 15]]
[[16 24 34 39]
[ 5 10 35 29]
[22 39 15 17]]
2. 插入和删除
和列表一样,我们可以给 numpy 数组:
- 用insert()函数在某个特定位置之前插入元素
- 用delete()函数删除某些特定元素
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]
3. 复制
用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]
五、数组的计算
本节介绍四大类数组计算,具体有:
- 元素层面 (element-wise) 计算
- 线性代数 (linear algebra) 计算
- 元素整合 (element aggregation) 计算
- 广播机制 (broadcasting) 计算
5.1 元素层面计算
Numpy 数组元素层面计算包括:
- 二元运算 (binary operation):加减乘除
- 数学函数:倒数、平方、指数、对数
- 比较运算 (comparison)
先定义两个数组 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)
# 倒数、平方、指数、对数
print(1 / arr1)
print(arr1 ** 2)
print(np.exp(arr1))
print(np.log(arr1))
# 比较
print(arr1 > arr2)
print(arr1 > 3)
[[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. ]]
[[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]]
[[False False True]
[ True True True]]
[[False False False]
[ True True True]]
5.2 线性代数计算
在 NumPy 默认不采用矩阵运算,而是数组 (ndarray) 运算。矩阵只是二维,而数组可以是任何维度,因此数组运算更通用些。
注:arr 的形状是 (2,),只含一个元素的元组只说明 arr 是一维,数组是不分行数组或列数组的。
下面我们分别对「数组」和「矩阵」从创建、转置、求逆和相乘四个方面看看它们的同异。
1. 创建
创建数组 arr2d 和矩阵 A,注意它们的输出有 array 和 matrix 的关键词。
arr2 = np.array([[1, 2], [3, 1]])
arr2
array([[1, 2],
[3, 1]])
A = np.asmatrix(arr2)
matrix([[1, 2],
[3, 1]])
2. 转置
数组用 arr2.T 操作或 arr.tranpose() 函数,而矩阵用 A.T 操作。主要原因就是 .T 只适合二维数据。
print(arr2.T)
print(arr2.transpose())
print(A.T)
[[1 3]
[2 1]]
[[1 3]
[2 1]]
[[1 3]
[2 1]]
3. 求逆
数组用 np.linalg.inv() 函数,而矩阵用 A.I 和 A**-1 操作。
print(np.linalg.inv(arr2))
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]]
4. 相乘
相乘是个很模棱两可的概念
- 数组相乘是在元素层面进行,
- 矩阵相乘要就是数学定义的矩阵相乘 (比如第一个矩阵的列要和第二个矩阵的行一样)
arr2 = np.array([[1, 2], [3, 1]])
A = np.asmatrix(arr2)
print(arr2*arr2) # 数组相乘
print(A*A) # 矩阵相乘
[[1 4]
[9 1]]
[[7 4]
[6 7]]
点乘函数 dot()
点乘左右两边最常见的数组就是:
- 向量 (1D) 和向量 (1D)
- 矩阵 (2D) 和向量 (1D)
- 矩阵 (2D) 和矩阵 (2D)
分别看看三个简单例子。
例一: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]]
当 x 第二个维度的元素 (x.shape[1]) 和 y 第一个维度的元素 (y.shape[0]) 个数相等时,np.dot(X, Y) 才有意义。
5.3 元素整合计算
在数组中,元素可以以不同方式整合 (aggregation)。拿求和 (sum) 函数来说,我们可以对数组:
- 所有的元素求和
- 在某个轴 (axis) 上的元素求和
arr = np.arange(1, 7).reshape((2, 3))
print(arr)
print( '总和:', arr.sum() )
print( '跨行求和', arr.sum(axis=0) )
print( '跨列求和', arr.sum(axis=1) )
[[1 2 3]
[4 5 6]]
总和: 21
跨行求和 [5 7 9]
跨列求和 [ 6 15]
行和列这些概念对矩阵 (二维矩阵) 才适用,高维矩阵还是要用轴 (axis) 来区分每个维度。
除了 sum 函数,整合函数还包括 min, max, mean, std 和 cumsum,分别是求最小值、最大值、均值、标准差和累加,这些函数对数组里的元素整合方式和 sum 函数相同,就不多讲了。
5.4 广播机制计算
当对两个形状不同的数组按元素操作时,可能会触发「广播机制」。具体做法,先适当复制元素使得这两个数组形状相同后再按元素操作,两个步骤:
- 广播轴 (broadcast axis):比对两个数组的维度,将形状小的数组的维度 (轴) 补齐
- 复制元素:顺着补齐的轴,将形状小的数组里的元素复制,使得最终形状和另一个数组吻合
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 的所有的行上。
广播机制的规则
当我们对两个数组操作时,如果它们的形状
- 不相容 (incompatible),广播机制不能进行
- 相容 (compatible),广播机制可以进行
因此,进行广播机制分两步
- 检查两个数组形状是否兼容,即从两个形状元组最后一个元素,来检查(它们是否相等,是否有一个等于 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,)。
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('a的维度:', a.ndim, 'a的形状:', a.shape)
print('b1的维度:', b1.ndim, 'b1的形状:', b1.shape)
print('b2的维度:', b2.ndim, 'b2的形状:', b2.shape)
print('b3的维度:', b3.ndim, 'b3的形状:', b3.shape)
print('b4的维度:', b4.ndim, 'b4的形状:', b4.shape)
print('b5的维度:', b5.ndim, 'b5的形状:', b5.shape)
a的维度: 3 a的形状: (1, 2, 3)
b1的维度: 2 b1的形状: (3, 3)
b2的维度: 2 b2的形状: (1, 3)
b3的维度: 2 b3的形状: (2, 3)
b4的维度: 3 b4的形状: (2, 2, 3)
b5的维度: 3 b5的形状: (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)
<ipython-input-159-224e02cbb1e0> in <module>
----> 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)
c3 = a + b3
print(c3)
print(c3.shape)
c4 = a + b4
print(c4)
print(c4.shape)
c5 = a + b5
print(c5)
print(c5.shape)
[[[1 3 5]
[4 6 8]]]
(1, 2, 3)
[[[ 1 3 5]
[ 7 9 11]]]
(1, 2, 3)
[[[ 1 3 5]
[ 7 9 11]]
[[ 7 9 11]
[13 15 17]]]
(2, 2, 3)
[[[ 1 3 5]
[ 4 6 8]]
[[ 4 6 8]
[ 7 9 11]]]
(2, 2, 3)