向量a= [1 2 3]
16 NumPy
数值计算是科学研究领域使用最多的编程功能。虽然Python自带了一些基本的数学函数以及列表、元组等数据结构,然而并没有对向量运算的天然支持,因而我们需要NumPy这个包来支持Python中的向量运算。
NumPy提供了丰富的数值计算方法,由于篇幅所限,我们无法介绍所有的NumPy特性,更多的参考可以从NumPy(包括SciPy)的网站: https://docs.scipy.org/doc/ 找到更加详细的参考。
16.1 向量、矩阵及其运算
NumPy最核心的是提供了向量和矩阵两个数据结构,可以方便的使用这些数据结构进行向量的加、减、数乘,以及矩阵的加、减、数乘、乘法、求逆、矩阵分解等运算。
在Python中,如果需要使用NumPy,首先要使用pip安装numpy,接着在文件中导入numpy。numpy.array()函数提供了将一个列表转化为一个向量的方法,比如:
此外,还有一些其他的方法创建向量,比如:
b= [ 1. 1. 1.]
b0= [ 0. 0. 0.]
还有一个更加常用的函数:linespace(n1,n2,N),该函数创建从n1到n2区间平均分为N等份的网格点:
0到5等分20等分:
[ 0. 0.26315789 0.52631579 0.78947368 1.05263158 1.31578947
1.57894737 1.84210526 2.10526316 2.36842105 2.63157895 2.89473684
3.15789474 3.42105263 3.68421053 3.94736842 4.21052632 4.47368421
4.73684211 5. ]
NumPy支持很多向量运算,比如:
print("基本数学运算:元素对元素运算")
print('数乘:3*a=',3*a)
print('a-b=',a-b)
print('a+b=',a+b)
print('a*b=',a*b)
print('b/a=',b/a)
print('向量运算')
c=np.dot(a,b) #a.*b
print('点乘(内积)a.*b=',c)
d=np.outer(a,b)
print('外积(a,b)=\n',d)
基本数学运算:元素对元素运算
数乘:3*a= [3 6 9]
a-b= [ 0. 1. 2.]
a+b= [ 2. 3. 4.]
a*b= [ 1. 2. 3.]
b/a= [ 1. 0.5 0.33333333]
向量运算
点乘(内积)a.*b= 6.0
外积(a,b)=
[[ 1. 1. 1.]
[ 2. 2. 2.]
[ 3. 3. 3.]]
这里要特别特别注意的是,向量的乘法*和除法定义的是元素对元素的乘除法,这点与MATLAB等有很大不同。
除了可以使用numpy.array()创建向量,也可以创建矩阵,创建矩阵时,提供一个列表的列表,每个列表代表矩阵中的一行:
以及一些特殊矩阵:
M=np.zeros((3,3)) #全为0的矩阵
print("矩阵M=\n",M)
O=np.ones((3,3)) #全为1的矩阵
print("矩阵O=\n",O)
I=np.eye(3) #单位阵
print("矩阵I=\n",I)
矩阵M=
[[ 0. 0. 0.]
[ 0. 0. 0.]
[ 0. 0. 0.]]
矩阵O=
[[ 1. 1. 1.]
[ 1. 1. 1.]
[ 1. 1. 1.]]
矩阵I=
[[ 1. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 1.]]
以及一些矩阵的运算:
print("A+I=\n",A+I)
print("A-I=\n",A-I)
print("A*I=\n",A*I)
print("I/A=\n",I/A)
print("矩阵相乘,A*O=\n", np.dot(A,O))
print("矩阵相乘,O*A=\n", np.dot(O,A))
print("矩阵转置,tranpose(A)=\n", A.transpose())
print("矩阵的逆,inv(A)=\n", np.linalg.inv(A))
l,L=np.linalg.eig(A)
print("矩阵的特征值=\n", l)
print("矩阵的特征向量=\n", L)
A+I=
[[ 2. 2. 3.]
[ 4. 9. 6.]
[ 4. 3. 3.]]
A-I=
[[ 0. 2. 3.]
[ 4. 7. 6.]
[ 4. 3. 1.]]
A*I=
[[ 1. 0. 0.]
[ 0. 8. 0.]
[ 0. 0. 2.]]
I/A=
[[ 1. 0. 0. ]
[ 0. 0.125 0. ]
[ 0. 0. 0.5 ]]
矩阵相乘,A*O=
[[ 6. 6. 6.]
[ 18. 18. 18.]
[ 9. 9. 9.]]
矩阵相乘,O*A=
[[ 9. 13. 11.]
[ 9. 13. 11.]
[ 9. 13. 11.]]
矩阵转置,tranpose(A)=
[[1 4 4]
[2 8 3]
[3 6 2]]
矩阵的逆,inv(A)=
[[ 0.06666667 -0.16666667 0.4 ]
[-0.53333333 0.33333333 -0.2 ]
[ 0.66666667 -0.16666667 0. ]]
矩阵的特征值=
[ 11.80142315 -2.04468118 1.24325803]
矩阵的特征向量=
[[ 0.26952559 0.61195333 0.44683972]
[ 0.88453378 0.21152352 -0.72721843]
[ 0.3807308 -0.76208328 0.52104474]]
同样,乘法*和除法定义的是元素对元素的乘除法,切记!
如果需要提取出矩阵的元素,可以使用 A[row,col](而非A[row][col]!!)。
此外,数组和矩阵都支持切片操作。比如,如果希望提取出矩阵A的奇数行、奇数列,可以用:
另外,这里与Python中的另外一个不同是,在Python的列表中,实行切片操作是直接对切片出的元素进行复制,而在NumPy中,切片操作只是一个视图(view),并没有进行复制。理解这一点是非常关键的,比如:
lista=[1,2,3,4]
suba=lista[1:3]
suba[0]=1
print(lista)
print(suba)
veca=np.array([1,2,3,4])
suba=veca[1:3]
suba[0]=1
print(veca)
print(suba)
[1, 2, 3, 4]
[1, 3]
[1 1 3 4]
[1 3]
因而,在对数组、矩阵进行切片、修改时,需要特别注意。
如果需要创建切片的副本,需要用copy()方法:
可以发现原始向量并没有被改变。
16.2 向量、矩阵的变形、合并和分裂
对于向量、矩阵的另外一个更常用的操作是变形,比如,我们可能将一个向量变形为一个矩阵,可以按照如下的方法:
vec_a=np.linspace(0,8,9)
print("vec_a:\n",vec_a)
mat_a=vec_a.reshape(3,3)
print("mat_a:\n",mat_a)
vec_b=vec_a.reshape(9,1)
print("vec_b:\n",vec_b)
vec_a:
[ 0. 1. 2. 3. 4. 5. 6. 7. 8.]
mat_a:
[[ 0. 1. 2.]
[ 3. 4. 5.]
[ 6. 7. 8.]]
vec_b:
[[ 0.]
[ 1.]
[ 2.]
[ 3.]
[ 4.]
[ 5.]
[ 6.]
[ 7.]
[ 8.]]
实际上,我们可以通过向量或者矩阵的ndim属性查看其维度:
print("Dimension of vec_a:%2d" % vec_a.ndim)
print("Dimension of vec_b:%2d" % vec_b.ndim)
print("Dimension of mat_a:%2d" % mat_a.ndim)
Dimension of vec_a: 1
Dimension of vec_b: 2
Dimension of mat_a: 2
可见,vec_b作为列向量,在NumPy中实际上看成时一个矩阵。如果要看其维数,需要用shape属性:
print("Dimension of vec_a:%2d" % vec_a.shape)
print("Dimension of vec_b:%2d ×%2d" % vec_b.shape)
print("Dimension of mat_a:%2d ×%2d" % mat_a.shape)
Dimension of vec_a: 9
Dimension of vec_b: 9 × 1
Dimension of mat_a: 3 × 3
也可以只用size属性查看其大小:
print("Dimension of vec_a:%2d" % vec_a.size)
print("Dimension of vec_b:%2d" % vec_b.size)
print("Dimension of mat_a:%2d" % mat_a.size)
Dimension of vec_a: 9
Dimension of vec_b: 9
Dimension of mat_a: 9
此外,有时我们还需要拼接两个矩阵,此时需要使用np.concatenate、np.vstack、np.hstack三个方法。
np.concatenate方法用于拼接向量,比如:
[1 2 3 4 5 6 7 8 9]
np.vstack用于竖直拼接矩阵,hstack用于水平拼接举着,比如:
mat_a=np.linspace(0,8,9).reshape(3,3)
mat_b=np.linspace(10,18,9).reshape(3,3)
print("mat_a:\n",mat_a)
print("mat_b:\n",mat_b)
mat_hab=np.hstack([mat_a,mat_b])
print("mat_hab:\n",mat_hab)
mat_vab=np.vstack([mat_a,mat_b])
print("mat_hab:\n",mat_vab)
mat_a:
[[ 0. 1. 2.]
[ 3. 4. 5.]
[ 6. 7. 8.]]
mat_b:
[[ 10. 11. 12.]
[ 13. 14. 15.]
[ 16. 17. 18.]]
mat_hab:
[[ 0. 1. 2. 10. 11. 12.]
[ 3. 4. 5. 13. 14. 15.]
[ 6. 7. 8. 16. 17. 18.]]
mat_hab:
[[ 0. 1. 2.]
[ 3. 4. 5.]
[ 6. 7. 8.]
[ 10. 11. 12.]
[ 13. 14. 15.]
[ 16. 17. 18.]]
16.3 随机数生成
科学计算中很多算法都依赖于随机数的生成,比如数值积分计算的Monte Carlo法、MCMC等方法。在Python中,自带了numpy.random包,可以用来生成随机数。
为了使用numpy.random,必须先导入:
接下来,就可以直接使用了。比如,
nprd.random(n)
产生一个 n 维向量,每个分量都服从均匀分布的随机数;nprd.randn(n)
产生一个 n 维向量,每个分量都服从正态分布的随机数;nprd.choice(a)
从向量 a 中随机抽取一个元素
……
具体随机数列表可以查看 numpy - random number。
16.4 向量函数
在此之前,我们曾经介绍过使用 Python 中自带的 math.exp()
、math.sin()
、math.cos()
等函数。现在想象以下,为了计算一个向量每个分量的指数函数,我们不得不写一个循环:
[ 2.71828183 7.3890561 20.08553692]
然而,Python 的循环非常的缓慢,如果数据量巨大,以上做法非常耗时。幸运的是,NumPy 为我们提供了非常强大的函数功能,比如:
a=np.array([1,0.5,3])
print('a=',a)
print('cos(a)=',np.cos(a))
print('exp(a)=',np.exp(a))
print('ln(a)=',np.log(a))
print('abs(a)=',np.abs(a))
a= [ 1. 0.5 3. ]
cos(a)= [ 0.54030231 0.87758256 -0.9899925 ]
exp(a)= [ 2.71828183 1.64872127 20.08553692]
ln(a)= [ 0. -0.69314718 1.09861229]
abs(a)= [ 1. 0.5 3. ]
函数列表可以从 Universal functions (ufunc) 中找到。
此外,NumPy 还提供了方便的数据加总函数,比如求和、平均数、最大值、最小值、中位数等。
比如,以下代码中,我们随机产生了一组正态分布的数据,并计算了其和和、平均数、最大值、最小值、中位数、四分位数:
import numpy as np
import numpy.random as nprd
a = nprd.randn(1000)
print("%-10s:%10.4f" % ("和", np.sum(a)))
print("%-10s:%10.4f" % ("均值", np.mean(a)))
print("%-10s:%10.4f" % ("最大值", np.max(a)))
print("%-10s:%10d" % ("最大值索引", np.argmax(a)))
print("%-10s:%10.4f" % ("最小值", np.min(a)))
print("%-10s:%10d" % ("最小值索引", np.argmin(a)))
print("%-10s:%10.4f" % ("中位数", np.median(a)))
print("%-10s:%10.4f" % ("上四分位数", np.percentile(a, 75)))
print("%-10s:%10.4f" % ("下四分位数", np.percentile(a, 25)))
print("%-10s:%10.4f" % ("标准差", np.std(a)))
print("%-10s:%10.4f" % ("方差", np.var(a)))
和 : 10.9811
均值 : 0.0110
最大值 : 3.6534
最大值索引 : 664
最小值 : -3.4083
最小值索引 : 91
中位数 : 0.0322
上四分位数 : 0.6480
下四分位数 : -0.6401
标准差 : 0.9673
方差 : 0.9356
不过,以上代码可能会有问题,如果向量中存在缺失值(比如NaN),以上函数也会计算出NaN。为了避免这一个问题,可以使用以上程序的安全版本:
print("%s:%f" % ("和",np.nansum(a)))
print("%s:%f" % ("均值",np.nanmean(a)))
print("%s:%f" % ("最大值",np.nanmax(a)))
print("%s:%f" % ("最大值索引",np.nanargmax(a)))
print("%s:%f" % ("最小值",np.nanmin(a)))
print("%s:%f" % ("最小值索引",np.nanargmin(a)))
print("%s:%f" % ("中位数",np.nanmedian(a)))
print("%s:%f" % ("上四分位数",np.nanpercentile(a,75)))
print("%s:%f" % ("上四分位数",np.nanpercentile(a,25)))
print("%s:%f" % ("标准差",np.nanstd(a)))
print("%s:%f" % ("方差",np.nanvar(a)))
和:10.981102
均值:0.010981
最大值:3.653389
最大值索引:664.000000
最小值:-3.408319
最小值索引:91.000000
中位数:0.032241
上四分位数:0.648012
上四分位数:-0.640122
标准差:0.967257
方差:0.935585
此外还有两个特殊的函数:numpy.any()函数用于判断一个逻辑向量(其值为True/False)是否有True;而numpy.all()用于判断逻辑向量是否全为真,比如:
16.5 SciPy 简介
SciPy
在 NumPy
的基础扩展了一些用于数值计算的高级工具,比如:
scipy.special
:特殊函数,包括gamma函数、beta函数、各种统计函数、erf函数等等,都可以从这里找到scipy.optimize
:提供了最优化方法scipy.sparse
:稀疏矩阵scipy.interpolate
:插值scipy.integrate
:积分和常微分方程scipy.fftpack
:快速傅里叶变换scipy.stats
:常用的统计函数
……
在此我们不再赘述,如有需要,可以参考 SciPy User Guide。我们也会在接下来通过实例的方式稍微介绍SciPy的用法。